fatigue 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,146 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/testtask'
49
+ Rake::TestTask.new(:test) do |test|
50
+ test.libs << 'lib' << 'test'
51
+ test.pattern = 'test/**/test_*.rb'
52
+ test.verbose = true
53
+ end
54
+
55
+ desc "Generate RCov test coverage and open in your browser"
56
+ task :coverage do
57
+ require 'rcov'
58
+ sh "rm -fr coverage"
59
+ sh "rcov test/test_*.rb"
60
+ sh "open coverage/index.html"
61
+ end
62
+
63
+ require 'rake/rdoctask'
64
+ Rake::RDocTask.new do |rdoc|
65
+ rdoc.rdoc_dir = 'rdoc'
66
+ rdoc.title = "#{name} #{version}"
67
+ rdoc.rdoc_files.include('README*')
68
+ rdoc.rdoc_files.include('lib/**/*.rb')
69
+ end
70
+
71
+ desc "Open an irb session preloaded with this library"
72
+ task :console do
73
+ sh "irb -rubygems -r ./lib/#{name}.rb"
74
+ end
75
+
76
+ #############################################################################
77
+ #
78
+ # Custom tasks (add your own tasks here)
79
+ #
80
+ #############################################################################
81
+
82
+
83
+
84
+ #############################################################################
85
+ #
86
+ # Packaging tasks
87
+ #
88
+ #############################################################################
89
+
90
+ task :release => :build do
91
+ unless `git branch` =~ /^\* master$/
92
+ puts "You must be on the master branch to release!"
93
+ exit!
94
+ end
95
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
96
+ sh "git tag v#{version}"
97
+ sh "git push origin master"
98
+ sh "git push origin v#{version}"
99
+ sh "gem push pkg/#{name}-#{version}.gem"
100
+ end
101
+
102
+ task :build => :gemspec do
103
+ sh "mkdir -p pkg"
104
+ sh "gem build #{gemspec_file}"
105
+ sh "mv #{gem_file} pkg"
106
+ end
107
+
108
+ task :gemspec => :validate do
109
+ # read spec file and split out manifest section
110
+ spec = File.read(gemspec_file)
111
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
112
+
113
+ # replace name version and date
114
+ replace_header(head, :name)
115
+ replace_header(head, :version)
116
+ replace_header(head, :date)
117
+ #comment this out if your rubyforge_project has a different name
118
+ replace_header(head, :rubyforge_project)
119
+
120
+ # determine file list from git ls-files
121
+ files = `git ls-files`.
122
+ split("\n").
123
+ sort.
124
+ reject { |file| file =~ /^\./ }.
125
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
126
+ map { |file| " #{file}" }.
127
+ join("\n")
128
+
129
+ # piece file back together and write
130
+ manifest = " s.files = %w[\n#{files}\n ]\n"
131
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
132
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
133
+ puts "Updated #{gemspec_file}"
134
+ end
135
+
136
+ task :validate do
137
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
138
+ unless libfiles.empty?
139
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
140
+ exit!
141
+ end
142
+ unless Dir['VERSION*'].empty?
143
+ puts "A `VERSION` file at root level violates Gem best practices."
144
+ exit!
145
+ end
146
+ end
data/bin/fatigue ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
+
5
+ require 'rubygems'
6
+ require 'fatigue'
7
+
8
+ art_lol = <<ART
9
+
10
+ __ _ _
11
+ / _| | | (_)
12
+ | |_ __ _| |_ _ __ _ _ _ ___
13
+ | _/ _` | __| |/ _` | | | |/ _ \\
14
+ | || (_| | |_| | (_| | |_| | __/
15
+ |_| \\__,_|\\__|_|\\__, |\\__,_|\\___|
16
+ __/ | for nike+garmin
17
+ |___/
18
+
19
+ ART
20
+
21
+ puts "\n"
22
+ puts art_lol
23
+ puts "\n"
24
+
25
+ print 'Nike+ User ID number: '
26
+ id = gets.chomp
27
+
28
+ print 'Garmin Connect Username: '
29
+ garmin_username = gets.chomp
30
+
31
+ print 'Garmin Connect Password: '
32
+ system "stty -echo"
33
+ garmin_password = gets.chomp
34
+ system "stty echo"
35
+
36
+ nike = Fatigue::Nike.new(id)
37
+ garmin = Fatigue::Garmin.new(garmin_username,garmin_password)
38
+
39
+ puts "\n\nHARDCORE RUNNING ACTION: "
40
+
41
+ if garmin.login
42
+ number = garmin.post_runs(nike.runs)
43
+
44
+ puts <<DONE
45
+
46
+
47
+
48
+ Neato! We just imported #{number} run#{'s' if number!=1}. See them for yourself at:
49
+ http://connect.garmin.com/activities
50
+
51
+ DONE
52
+
53
+ else
54
+ puts "\nThere was a problem logging you into Garmin Connect."
55
+ end
data/fatigue.gemspec ADDED
@@ -0,0 +1,85 @@
1
+ ## This is the rakegem gemspec template. Make sure you read and understand
2
+ ## all of the comments. Some sections require modification, and others can
3
+ ## be deleted if you don't need them. Once you understand the contents of
4
+ ## this file, feel free to delete any comments that begin with two hash marks.
5
+ ## You can find comprehensive Gem::Specification documentation, at
6
+ ## http://docs.rubygems.org/read/chapter/20
7
+ Gem::Specification.new do |s|
8
+ s.specification_version = 2 if s.respond_to? :specification_version=
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.rubygems_version = '1.3.5'
11
+
12
+ ## Leave these as is they will be modified for you by the rake gemspec task.
13
+ ## If your rubyforge_project name is different, then edit it and comment out
14
+ ## the sub! line in the Rakefile
15
+ s.name = 'fatigue'
16
+ s.version = '0.0.1'
17
+ s.date = '2010-09-12'
18
+ s.rubyforge_project = 'fatigue'
19
+
20
+ ## Make sure your summary is short. The description may be as long
21
+ ## as you like.
22
+ s.summary = "Import your Nike+ runs into Garmin Connect."
23
+ s.description = "A tiny little gem that helps you load your Nike+ run history
24
+ into Garmin Connect. Cool, huh?."
25
+
26
+ ## List the primary authors. If there are a bunch of authors, it's probably
27
+ ## better to set the email to an email list or something. If you don't have
28
+ ## a custom homepage, consider using your GitHub URL or the like.
29
+ s.authors = ["Zach Holman"]
30
+ s.email = 'fatigue@zachholman.com'
31
+ s.homepage = 'http://github.com/holman/fatigue'
32
+
33
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
34
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
35
+ s.require_paths = %w[lib]
36
+
37
+ ## This sections is only necessary if you have C extensions.
38
+ # s.require_paths << 'ext'
39
+ # s.extensions = %w[ext/extconf.rb]
40
+
41
+ ## If your gem includes any executables, list them here.
42
+ s.executables = ["fatigue"]
43
+ s.default_executable = 'fatigue'
44
+
45
+ ## Specify any RDoc options here. You'll want to add your README and
46
+ ## LICENSE files to the extra_rdoc_files list.
47
+ s.rdoc_options = ["--charset=UTF-8"]
48
+ s.extra_rdoc_files = %w[readme.markdown]
49
+
50
+ ## List your runtime dependencies here. Runtime dependencies are those
51
+ ## that are needed for an end user to actually USE your code.
52
+ s.add_dependency('nokogiri', ">= 1.4.0")
53
+ s.add_dependency('mechanize', '1.0.0')
54
+ s.add_dependency('ruby-progressbar', '0.0.9')
55
+
56
+ ## List your development dependencies here. Development dependencies are
57
+ ## those that are only needed during development
58
+ s.add_development_dependency('rr', '0.10.11')
59
+
60
+ ## Leave this section as-is. It will be automatically generated from the
61
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
62
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
63
+ # = MANIFEST =
64
+ s.files = %w[
65
+ Rakefile
66
+ bin/fatigue
67
+ fatigue.gemspec
68
+ lib/fatigue.rb
69
+ lib/fatigue/garmin.rb
70
+ lib/fatigue/nike.rb
71
+ lib/fatigue/run.rb
72
+ readme.markdown
73
+ test/fixtures/runs.xml
74
+ test/fixtures/user.xml
75
+ test/helper.rb
76
+ test/test_garmin.rb
77
+ test/test_nike.rb
78
+ test/test_run.rb
79
+ ]
80
+ # = MANIFEST =
81
+
82
+ ## Test files will be grabbed from the file list. Make sure the path glob
83
+ ## matches what you actually use.
84
+ s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
85
+ end
data/lib/fatigue.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'fatigue/garmin'
2
+ require 'fatigue/nike'
3
+ require 'fatigue/run'
4
+
5
+ require 'nokogiri'
6
+ require 'open-uri'
7
+ require 'mechanize'
8
+ require 'progressbar'
9
+
10
+ module Fatigue
11
+ VERSION = '0.0.1'
12
+ end
@@ -0,0 +1,110 @@
1
+ module Fatigue
2
+
3
+ # GARMIN
4
+ # A class used to access Garmin Connect.
5
+ class Garmin
6
+
7
+ # Public: Create a new Garmin instance.
8
+ #
9
+ # username - The String of the Garmin Connect username.
10
+ # password - The String of the Garmin Connect password.
11
+ #
12
+ # Returns a new Fatigue::Garmin instance.
13
+ def initialize(username, password)
14
+ @username = username
15
+ @password = password
16
+ end
17
+
18
+ # Public: logs the instance into Garmin Connect.
19
+ #
20
+ # Returns true if successful login, false if unsuccessful.
21
+ def login
22
+ @agent = Mechanize.new
23
+ login_page = @agent.get('https://connect.garmin.com/signin')
24
+ form = login_page.form('login')
25
+ form['login:loginUsernameField'] = @username
26
+ form['login:password'] = @password
27
+ page = @agent.submit(form, form.buttons.first)
28
+ page.inspect =~ /Dashboard for/ ? true : false
29
+ end
30
+
31
+ # Public: logs the instance out of Garmin Connect.
32
+ #
33
+ # This is pretty important since Garmin hates multi-logins. Apparently?
34
+ #
35
+ # Returns true if successful logout, false if unsuccessful.
36
+ def logout
37
+ page = @agent.get('http://connect.garmin.com/dashboard')
38
+ page.link_with(:text => 'Sign Out').click
39
+ page.inspect =~ /aren't signed in/ ? true : false
40
+ end
41
+
42
+ # Public: posts a new Run to your logged-in Garmin account.
43
+ #
44
+ # run - A Fatigue::Run instance.
45
+ #
46
+ # Returns true if success, false if failure. Requires #login to be called
47
+ # prior to execution.
48
+ def post_run(run)
49
+ manual_run = @agent.get('http://connect.garmin.com/activity/manual')
50
+ form = manual_run.form('manualActivityForm')
51
+ form.activityBeginDate = run.started_at.strftime('%m/%d/%Y')
52
+ form.activityBeginTime = run.started_at.strftime('%I:%M %p')
53
+ form.field_with(:name => 'activityTimeZoneDecoration:activityTimeZone').
54
+ options.select { |option|
55
+ # select option where our timezones are equal (-07:00, etc)
56
+ option.text.gsub(':','') =~ /#{run.started_at.strftime('%z')}/
57
+ }.first.select
58
+ form['activityNameDecoration:activityName'] = run.formatted_name
59
+ form.field_with(:name => 'activityTypeDecoration:activityType').
60
+ options.select { |option| option.value == 'running' }.
61
+ first.select
62
+ form['speedPaceContainer:activitySummarySumDuration'] = run.hours
63
+ form['speedPaceContainer:activitySummarySumDurationMinute'] = run.minutes
64
+ form['speedPaceContainer:activitySummarySumDurationSecond'] = run.seconds
65
+ form['speedPaceContainer:activitySummarySumDistanceDecoration:activitySummarySumDistance'] = run.distance
66
+ form.field_with(:name => 'speedPaceContainer:activitySummarySumDistanceDecoration:distanceUnit').
67
+ options.select { |option| option.text == run.unit }.first.select
68
+ form['descriptionDecoration:description'] = run.description
69
+ form['calories'] = run.calories
70
+ form['AJAXREQUEST'] = '_viewRoot'
71
+ form['saveButton'] = 'saveButton'
72
+
73
+ resp = @agent.submit(form, form.buttons_with(:name => 'saveButton').first)
74
+
75
+ verify_response(resp.body)
76
+ end
77
+
78
+ # Public: Posts a collection of Runs to your logged-in Garmin account.
79
+ #
80
+ # runs - An Array of Fatigue::Run instances.
81
+ #
82
+ # Returns the number of runs successfully posted to the Garmin account.
83
+ # Requires #login to be called prior to execution.
84
+ def post_runs(runs)
85
+ progress = ProgressBar.new(" status", runs.size)
86
+ runs.each do |run|
87
+ post_run(run)
88
+ progress.inc
89
+ end
90
+ progress.finish
91
+ runs.size
92
+ end
93
+
94
+
95
+ # INTERNAL METHODS #########################################################
96
+
97
+ # Verifies whether the response returned is successful or not. For some
98
+ # reason Garmin isn't spitting out a redirect to the newly-created run, so
99
+ # instead we hackishly check if an error <span> is filled.
100
+ #
101
+ # Returns a Boolean value for whether the response was successful or not.
102
+ def verify_response(html_body)
103
+ html = Nokogiri::HTML(html_body)
104
+ html.css('#ErrorContainer').text.strip == ''
105
+ end
106
+
107
+
108
+ end
109
+
110
+ end
@@ -0,0 +1,72 @@
1
+ module Fatigue
2
+
3
+ # NIKE
4
+ # Nike grabs your runs from Nike
5
+ class Nike
6
+
7
+ # Public: Create a new Nike+ instance.
8
+ #
9
+ # id - Numeric Nike+ user ID (see readme.markdown for determining this)
10
+ #
11
+ # Returns a fresh Nike instance.
12
+ def initialize(id)
13
+ @id = id
14
+ end
15
+
16
+ # Public: Get all the runs for this user
17
+ #
18
+ # Returns an array of Fatigue::Run instances.
19
+ def runs
20
+ #structure = Nokogiri::XML(user_data_xml)
21
+ #unit = structure.xpath('.//plusService/userOptions/distanceUnit').
22
+ # first.text
23
+ structure = Nokogiri::XML(run_list_xml)
24
+ structure.xpath('.//plusService/runList/run').inject([]) do |list,xml|
25
+ run = Fatigue::Run.new
26
+ run.service = 'Nike+'
27
+ run.unit = 'km' # I think Nike+ hardcodes km
28
+ run.name = get_value('name',xml)
29
+ run.distance = get_value('distance',xml)
30
+ run.duration = get_value('duration',xml)
31
+ run.started_at = get_value('startTime',xml)
32
+ run.calories = get_value('calories',xml)
33
+ run.description = get_value('description',xml)
34
+ list << run
35
+ end
36
+ end
37
+
38
+
39
+ # INTERNAL METHODS #########################################################
40
+
41
+
42
+ # Builds the Nike+ XML url and retreives it.
43
+ #
44
+ # Returns a string of the list of your runs in XML format.
45
+ def run_list_xml
46
+ run_url = 'http://nikerunning.nike.com/nikeplus/v1/services/widget/get_public_run_list.jsp?userID='
47
+ run_url += @id.to_s
48
+ open(run_url)
49
+ end
50
+
51
+ # Builds the Nike+ user XML url and retreives it. This has unit data.
52
+ #
53
+ # Returns a string of your account in XML format.
54
+ def user_data_xml
55
+ run_url = 'http://nikerunning.nike.com/nikeplus/v1/services/widget/get_public_user_data.jsp?userID='
56
+ run_url += @id.to_s
57
+ open(run_url)
58
+ end
59
+
60
+ # Parses a line of XML for a given attribute.
61
+ #
62
+ # attribute - The node we are searching for.
63
+ # xml - The line of XML we should search in.
64
+ #
65
+ # Returns a scalar value of the attribute.
66
+ def get_value(attribute,xml)
67
+ xml.xpath(".//#{attribute}").first.content
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,108 @@
1
+ module Fatigue
2
+
3
+ # RUN
4
+ # An individual run knows about its distance, time, and other fun.
5
+ class Run
6
+
7
+ # Public: The name of the service used for this run.
8
+ #
9
+ # service: The name of the service used for this run.
10
+ attr_writer :service
11
+
12
+ # Public: The name of the service used for this run.
13
+ attr_reader :service
14
+
15
+
16
+ # Public: The name of the run.
17
+ #
18
+ # name: The name of the run used.
19
+ attr_writer :name
20
+
21
+ # Public: The name of the run.
22
+ attr_reader :name
23
+
24
+ # Public: The formatted name of the run. Includes service name.
25
+ def formatted_name
26
+ "#{service}#{name.strip == '' ? '' : ': '+name}"
27
+ end
28
+
29
+
30
+ # Public: The distance of the run.
31
+ #
32
+ # distance: The String unitless distance run.
33
+ attr_writer :distance
34
+
35
+ # Public: The distance of the run.
36
+ attr_reader :distance
37
+
38
+
39
+ # Public: The distance unit.
40
+ #
41
+ # unit: The string value of the unit. Permitted: "mi", "km".
42
+ attr_writer :unit
43
+
44
+ # Public: the full name of the distance unit.
45
+ def unit
46
+ case @unit
47
+ when 'mi' then 'Miles'
48
+ when 'km' then 'Kilometers'
49
+ end
50
+ end
51
+
52
+ # Public: The duration of the run.
53
+ #
54
+ # duration: The String number of milliseconds the run took.
55
+ attr_writer :duration
56
+
57
+ # Public: The Integer duration of the run.
58
+ def duration
59
+ @duration.to_i
60
+ end
61
+
62
+ # Public: The number of seconds in the run (reduced).
63
+ def seconds
64
+ (duration / 1000) % 60
65
+ end
66
+
67
+ # Public: The number of minutes in the run (reduced).
68
+ def minutes
69
+ (duration / 1000 / 60) % 60
70
+ end
71
+
72
+ # Public: The number of hours in the run (reduced).
73
+ def hours
74
+ (duration / 1000 / 60 / 60)
75
+ end
76
+
77
+
78
+ # Public: The time the run was started.
79
+ #
80
+ # started_at: The String time in ISO 8601.
81
+ attr_writer :started_at
82
+
83
+ # Public: The time the run was started.
84
+ def started_at
85
+ Time.iso8601 @started_at
86
+ end
87
+
88
+
89
+ # Public: The number of calories burned in the run.
90
+ #
91
+ # calories: The String number of calories.
92
+ attr_writer :calories
93
+
94
+ # Public: The number of calories burned in the run.
95
+ attr_reader :calories
96
+
97
+
98
+ # Public: The description of the run.
99
+ #
100
+ # description: The String details of the run.
101
+ attr_writer :description
102
+
103
+ # Public: The description of the run.
104
+ attr_reader :description
105
+
106
+ end
107
+
108
+ end
data/readme.markdown ADDED
@@ -0,0 +1,66 @@
1
+ # FATIGUE
2
+ **Import your Nike+ runs to Garmin Connect**
3
+
4
+ ## It looks like this
5
+
6
+ $ fatigue
7
+
8
+
9
+ __ _ _
10
+ / _| | | (_)
11
+ | |_ __ _| |_ _ __ _ _ _ ___
12
+ | _/ _` | __| |/ _` | | | |/ _ \
13
+ | || (_| | |_| | (_| | |_| | __/
14
+ |_| \__,_|\__|_|\__, |\__,_|\___|
15
+ __/ | for nike+garmin
16
+ |___/
17
+
18
+
19
+
20
+ Nike+ User ID: 123456789
21
+ Garmin Connect Username: sookie
22
+ Garmin Connect Password:
23
+
24
+ HARDCORE RUNNING ACTION:
25
+ status: 100% |oooooooooooooooooooooooooooooooooooooooooo| Time: 00:02:20
26
+
27
+ Neato! We just imported 72 runs. See them for yourself at:
28
+ http://connect.garmin.com/activities
29
+
30
+ ## What you get
31
+
32
+ This grabs run data from the low-fi Nike+ histories, so while it won't have GPS
33
+ maps like what Garmin rocks, we'll populate your Garmin history with Nike+ data
34
+ for things like distance run, how long your run took, what day and time it was
35
+ run, and nifty things like that.
36
+
37
+ ## Install
38
+
39
+ gem install fatigue
40
+
41
+ ## Can I see your ID
42
+
43
+ Nike might release a public API at some point in the future (although they'll
44
+ probably make you buy their shoes in order to use it). Until then, we can sneak
45
+ onto their widget API. You need to find your Nike+ user id, though (numeric ID,
46
+ that is; not your login).
47
+
48
+ To do this, log into your Nike+ account, click on "Runs", and then the long
49
+ number right at the end of the URL is just what you need. COPY AND PASTE IT
50
+ WITH ALL DUE HASTE.
51
+
52
+ You'll also need to make sure your runs are public. Click on your face on the
53
+ sidebar, go to settings, and share your life away. No need to be modest.
54
+
55
+ ## Contribute
56
+
57
+ Pretty sure Nike will change their site, Garmin will change their site, or
58
+ both, so this may be a bit unstable, from time to time. Have no fear; open
59
+ source is here.
60
+
61
+ If you'd like to contribute fixes (thanks!), fork this project, add your
62
+ changes, make sure tests pass with `rake`, and send me a pull request.
63
+ High-fives for new methods that are properly [TomDoc](http://tomdoc.org)'d.
64
+
65
+ # JUST DO IT™
66
+ Love, @[holman](http://twitter.com/holman).
@@ -0,0 +1,41 @@
1
+ <plusService>
2
+ <status>success</status>
3
+ <runList endIndex="-1" startIndex="0">
4
+ <run id="1" workoutType="standard">
5
+ <startTime>2009-03-17T04:02:43+00:00</startTime>
6
+ <distance>3</distance>
7
+ <duration>900000</duration>
8
+ <syncTime>2009-03-17T04:02:43+00:00</syncTime>
9
+ <calories>0.0</calories>
10
+ <name/>
11
+ <description/>
12
+ <howFelt/>
13
+ <weather/>
14
+ <terrain/>
15
+ <intensity/>
16
+ <gpxId/>
17
+ <equipmentType>ipod</equipmentType>
18
+ </run>
19
+ <run id="2" workoutType="standard">
20
+ <startTime>2008-05-21T13:28:59-05:00</startTime>
21
+ <distance>5</distance>
22
+ <duration>1000000</duration>
23
+ <syncTime>2008-05-21T18:59:38+00:00</syncTime>
24
+ <calories>244.0</calories>
25
+ <name><![CDATA[ran away from the pretty blonde]]></name>
26
+ <description><![CDATA[ran away from the pretty blonde]]></description>
27
+ <howFelt/>
28
+ <weather/>
29
+ <terrain/>
30
+ <intensity/>
31
+ <gpxId/>
32
+ <equipmentType>ipod</equipmentType>
33
+ </run>
34
+ </runList>
35
+ <runListSummary>
36
+ <runs>2</runs>
37
+ <distance>8</distance>
38
+ <duration>1900000</duration>
39
+ <calories>244.0</calories>
40
+ </runListSummary>
41
+ </plusService>
@@ -0,0 +1,59 @@
1
+ <plusService>
2
+ <status>success</status>
3
+ <user externalProfileID="1" id="1">
4
+ <status>confirmed</status>
5
+ <gender>female</gender>
6
+ <state>LA</state>
7
+ <city>Bon Temps</city>
8
+ <country>US</country>
9
+ <plusLevel>2</plusLevel>
10
+ <activeOwnedChallengeCount>0</activeOwnedChallengeCount>
11
+ </user>
12
+ <userTotals>
13
+ <totalDistance>52</totalDistance>
14
+ <totalDuration>10000000</totalDuration>
15
+ <totalRunsWithRoutes>0</totalRunsWithRoutes>
16
+ <totalRuns>5</totalRuns>
17
+ <totalCalories>10000</totalCalories>
18
+ <totalWorkouts>0</totalWorkouts>
19
+ <totalCardioDistance>0.0</totalCardioDistance>
20
+ <averageRunsPerWeek>1.0</averageRunsPerWeek>
21
+ <preferredRunDayOfWeek>Sunday </preferredRunDayOfWeek>
22
+ <pedoWorkouts>0</pedoWorkouts>
23
+ <totalSteps>0</totalSteps>
24
+ <longestStepcount>0</longestStepcount>
25
+ <averageStepcount>0.0</averageStepcount>
26
+ <caloriesPedometer>0</caloriesPedometer>
27
+ <totalCaloriesPedometer>0</totalCaloriesPedometer>
28
+ <totalRunsWithHeartrate>0</totalRunsWithHeartrate>
29
+ <previousSyncTime>2010-08-05T07:50:53+00:00</previousSyncTime>
30
+ <lastCalculated>2010-08-23T03:24:18+00:00</lastCalculated>
31
+ </userTotals>
32
+ <userOptions>
33
+ <screenName><![CDATA[sookie]]></screenName>
34
+ <distanceUnit>mi</distanceUnit>
35
+ <dateFormat>MM/DD/YY</dateFormat>
36
+ <startWeek>Su</startWeek>
37
+ <avatar>http://avatar.nikeplus.com/vampire.png</avatar>
38
+ <uploadedAvatar>http://avatar.nikeplus.com/vampire.png</uploadedAvatar>
39
+ <isPublic>true</isPublic>
40
+ <powerSong>
41
+ <artist><![CDATA[Jace Everett]]></artist>
42
+ <album><![CDATA[True Blood]]></album>
43
+ <title><![CDATA[Bad Things]]></title>
44
+ </powerSong>
45
+ <emailGoalEnding>false</emailGoalEnding>
46
+ <emailGoalComplete>false</emailGoalComplete>
47
+ <emailWeeklyTraining>false</emailWeeklyTraining>
48
+ <emailChallengeEnding>false</emailChallengeEnding>
49
+ <emailChallengeStarting>false</emailChallengeStarting>
50
+ <emailChallengeWinner>false</emailChallengeWinner>
51
+ </userOptions>
52
+ <mostRecentRun id="2" workoutType="standard">
53
+ <startTime>2010-08-04T19:37:47-07:00</startTime>
54
+ <distance>3.4539</distance>
55
+ <duration>976277</duration>
56
+ <workoutType>standard</workoutType>
57
+ <equipment>ipod</equipment>
58
+ </mostRecentRun>
59
+ </plusService>
data/test/helper.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'rr'
4
+
5
+ begin
6
+ require 'redgreen'
7
+ require 'leftright'
8
+ rescue LoadError
9
+ end
10
+
11
+ dir = File.dirname(File.expand_path(__FILE__))
12
+ $LOAD_PATH.unshift(File.join(dir, '..', 'lib'))
13
+ $LOAD_PATH.unshift(dir)
14
+
15
+ require 'fatigue'
16
+
17
+ def fixture(file)
18
+ test_dir = File.dirname(File.expand_path(__FILE__))
19
+ file = File.join(test_dir,'fixtures',file)
20
+ File.read(file)
21
+ end
22
+
23
+ # test/spec/mini 3
24
+ # http://gist.github.com/25455
25
+ # chris@ozmm.org
26
+ # file:lib/test/spec/mini.rb
27
+ def context(*args, &block)
28
+ return super unless (name = args.first) && block
29
+ require 'test/unit'
30
+ klass = Class.new(defined?(ActiveSupport::TestCase) ? ActiveSupport::TestCase : Test::Unit::TestCase) do
31
+ include RR::Adapters::TestUnit
32
+
33
+ def self.test(name, &block)
34
+ define_method("test_#{name.gsub(/\W/,'_')}", &block) if block
35
+ end
36
+ def self.xtest(*args) end
37
+ def self.setup(&block) define_method(:setup, &block) end
38
+ def self.teardown(&block) define_method(:teardown, &block) end
39
+ end
40
+ (class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
41
+ klass.class_eval &block
42
+ end
@@ -0,0 +1,19 @@
1
+ require File.join(File.dirname(__FILE__), *%w[helper])
2
+
3
+ context "Garmin" do
4
+
5
+ setup do
6
+ @garmin = Fatigue::Garmin.new('username','password')
7
+ end
8
+
9
+ test "verify response" do
10
+ error = @garmin.verify_response('<span id="ErrorContainer">An Error</span>')
11
+ assert_equal false, error
12
+
13
+ error = @garmin.verify_response('<span id="ErrorContainer"></span>')
14
+ assert_equal true, error
15
+
16
+ error = @garmin.verify_response('')
17
+ assert_equal true, error
18
+ end
19
+ end
data/test/test_nike.rb ADDED
@@ -0,0 +1,19 @@
1
+ require File.join(File.dirname(__FILE__), *%w[helper])
2
+
3
+ context "Nike" do
4
+
5
+ setup do
6
+ @nike = Fatigue::Nike.new(1)
7
+ end
8
+
9
+ test "runs" do
10
+ mock(@nike).run_list_xml { fixture('runs.xml') }
11
+ #mock(@nike).user_data_xml { fixture('user.xml') }
12
+ runs = @nike.runs
13
+
14
+ assert_equal runs.size, 2
15
+ assert_equal runs.first.distance, '3'
16
+ assert_equal runs.last.duration, 1000000
17
+ end
18
+
19
+ end
data/test/test_run.rb ADDED
@@ -0,0 +1,43 @@
1
+ require File.join(File.dirname(__FILE__), *%w[helper])
2
+
3
+ context "Run" do
4
+
5
+ setup do
6
+ @run = Fatigue::Run.new
7
+ end
8
+
9
+ test "basic setters" do
10
+ @run.duration = 5
11
+ assert_equal 5, @run.duration
12
+
13
+ @run.calories = 100
14
+ assert_equal 100, @run.calories
15
+ end
16
+
17
+ test "date conversion" do
18
+ @run.started_at = '2009-03-17T04:02:43+00:00'
19
+ assert_equal @run.started_at.to_s, 'Tue Mar 17 04:02:43 UTC 2009'
20
+ end
21
+
22
+ test "formatted name" do
23
+ @run.service = 'Nike+'
24
+ @run.name = 'I ran this while drunk'
25
+ assert_equal @run.formatted_name, 'Nike+: I ran this while drunk'
26
+ end
27
+
28
+ test "seconds" do
29
+ @run.duration = '1000000'
30
+ assert_equal 40, @run.seconds
31
+ end
32
+
33
+ test "minutes" do
34
+ @run.duration = 1000000
35
+ assert_equal 16, @run.minutes
36
+ end
37
+
38
+ test "hours" do
39
+ @run.duration = 5000000
40
+ assert_equal 1, @run.hours
41
+ end
42
+
43
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fatigue
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Zach Holman
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-12 00:00:00 -07:00
19
+ default_executable: fatigue
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: nokogiri
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 1
32
+ - 4
33
+ - 0
34
+ version: 1.4.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: mechanize
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - "="
44
+ - !ruby/object:Gem::Version
45
+ hash: 23
46
+ segments:
47
+ - 1
48
+ - 0
49
+ - 0
50
+ version: 1.0.0
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: ruby-progressbar
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - "="
60
+ - !ruby/object:Gem::Version
61
+ hash: 13
62
+ segments:
63
+ - 0
64
+ - 0
65
+ - 9
66
+ version: 0.0.9
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: rr
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - "="
76
+ - !ruby/object:Gem::Version
77
+ hash: 33
78
+ segments:
79
+ - 0
80
+ - 10
81
+ - 11
82
+ version: 0.10.11
83
+ type: :development
84
+ version_requirements: *id004
85
+ description: |-
86
+ A tiny little gem that helps you load your Nike+ run history
87
+ into Garmin Connect. Cool, huh?.
88
+ email: fatigue@zachholman.com
89
+ executables:
90
+ - fatigue
91
+ extensions: []
92
+
93
+ extra_rdoc_files:
94
+ - readme.markdown
95
+ files:
96
+ - Rakefile
97
+ - bin/fatigue
98
+ - fatigue.gemspec
99
+ - lib/fatigue.rb
100
+ - lib/fatigue/garmin.rb
101
+ - lib/fatigue/nike.rb
102
+ - lib/fatigue/run.rb
103
+ - readme.markdown
104
+ - test/fixtures/runs.xml
105
+ - test/fixtures/user.xml
106
+ - test/helper.rb
107
+ - test/test_garmin.rb
108
+ - test/test_nike.rb
109
+ - test/test_run.rb
110
+ has_rdoc: true
111
+ homepage: http://github.com/holman/fatigue
112
+ licenses: []
113
+
114
+ post_install_message:
115
+ rdoc_options:
116
+ - --charset=UTF-8
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ hash: 3
125
+ segments:
126
+ - 0
127
+ version: "0"
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 3
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ requirements: []
138
+
139
+ rubyforge_project: fatigue
140
+ rubygems_version: 1.3.7
141
+ signing_key:
142
+ specification_version: 2
143
+ summary: Import your Nike+ runs into Garmin Connect.
144
+ test_files:
145
+ - test/test_garmin.rb
146
+ - test/test_nike.rb
147
+ - test/test_run.rb