postrunner 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/postrunner/ActivitiesDB.rb +86 -31
- data/lib/postrunner/Activity.rb +222 -30
- data/lib/postrunner/ActivityLink.rb +59 -0
- data/lib/postrunner/ActivityListView.rb +37 -90
- data/lib/postrunner/ActivitySummary.rb +12 -12
- data/lib/postrunner/ActivityView.rb +49 -72
- data/lib/postrunner/BackedUpFile.rb +56 -0
- data/lib/postrunner/ChartView.rb +14 -16
- data/lib/postrunner/DeviceList.rb +3 -7
- data/lib/postrunner/FlexiTable.rb +28 -1
- data/lib/postrunner/HTMLBuilder.rb +64 -16
- data/lib/postrunner/Main.rb +63 -19
- data/lib/postrunner/NavButtonRow.rb +103 -0
- data/lib/postrunner/PagingButtons.rb +77 -0
- data/lib/postrunner/PersonalRecords.rb +338 -79
- data/lib/postrunner/RecordListPageView.rb +69 -0
- data/lib/postrunner/RuntimeConfig.rb +5 -3
- data/lib/postrunner/TrackView.rb +14 -16
- data/lib/postrunner/UserProfileView.rb +3 -7
- data/lib/postrunner/View.rb +97 -0
- data/lib/postrunner/ViewBottom.rb +54 -0
- data/lib/postrunner/ViewButtons.rb +68 -0
- data/lib/postrunner/ViewFrame.rb +93 -0
- data/lib/postrunner/ViewTop.rb +80 -0
- data/lib/postrunner/version.rb +1 -1
- data/misc/icons/activities.png +0 -0
- data/misc/icons/activities.svg +1582 -0
- data/misc/icons/record-small.png +0 -0
- data/misc/icons/record.png +0 -0
- data/misc/icons/record.svg +15712 -0
- data/spec/ActivitySummary_spec.rb +3 -1
- data/spec/PostRunner_spec.rb +45 -0
- data/spec/View_spec.rb +61 -0
- data/spec/spec_helper.rb +21 -7
- metadata +19 -3
- data/lib/postrunner/ViewWidgets.rb +0 -153
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = ActivitiesDB.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
|
+
#
|
6
|
+
# Copyright (c) 2015 by Chris Schlaeger <cs@taskjuggler.org>
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or modify
|
9
|
+
# it under the terms of version 2 of the GNU General Public License as
|
10
|
+
# published by the Free Software Foundation.
|
11
|
+
#
|
12
|
+
|
13
|
+
module PostRunner
|
14
|
+
|
15
|
+
# BackUpFile is a specialized version of File that creates a copy on
|
16
|
+
class BackedUpFile < File
|
17
|
+
|
18
|
+
def BackedUpFile.open(filename, mode = 'r', *opt, &block)
|
19
|
+
# If the file is opened for writing we create a backup file.
|
20
|
+
create_backup_file(filename) if mode.include?('w') || mode.include?('a')
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def BackedUpFile.write(filename, string)
|
25
|
+
create_backup_file(filename)
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def BackedUpFile.create_backup_file(filename)
|
32
|
+
bak_file = filename + '.bak'
|
33
|
+
|
34
|
+
# Delete the backup file if it exists.
|
35
|
+
if File.exists?(bak_file)
|
36
|
+
begin
|
37
|
+
File.delete(bak_file)
|
38
|
+
rescue SystemCallError
|
39
|
+
Log.fatal "Cannote remove backup file '#{bak_file}': #{$!}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Rename the old file to <filename>.bak
|
44
|
+
if File.exists?(filename)
|
45
|
+
begin
|
46
|
+
File.rename(filename, bak_file)
|
47
|
+
rescue SystemCallError
|
48
|
+
Log.fatal "Cannot rename file '#{filename}' to '#{bak_file}': #{$!}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
data/lib/postrunner/ChartView.rb
CHANGED
@@ -9,14 +9,10 @@
|
|
9
9
|
# published by the Free Software Foundation.
|
10
10
|
#
|
11
11
|
|
12
|
-
require 'postrunner/ViewWidgets'
|
13
|
-
|
14
12
|
module PostRunner
|
15
13
|
|
16
14
|
class ChartView
|
17
15
|
|
18
|
-
include ViewWidgets
|
19
|
-
|
20
16
|
def initialize(activity, unit_system)
|
21
17
|
@activity = activity
|
22
18
|
@sport = activity.fit_activity.sessions[0].sport
|
@@ -24,17 +20,19 @@ module PostRunner
|
|
24
20
|
@empty_charts = {}
|
25
21
|
end
|
26
22
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
23
|
+
def to_html(doc)
|
24
|
+
doc.unique(:chartview_style) {
|
25
|
+
doc.head {
|
26
|
+
[ 'jquery/jquery-2.1.1.min.js', 'flot/jquery.flot.js',
|
27
|
+
'flot/jquery.flot.time.js' ].each do |js|
|
28
|
+
doc.script({ 'language' => 'javascript',
|
29
|
+
'type' => 'text/javascript', 'src' => js })
|
30
|
+
end
|
31
|
+
doc.style(style)
|
32
|
+
}
|
33
|
+
}
|
36
34
|
|
37
|
-
|
35
|
+
doc.script(java_script)
|
38
36
|
if @sport == 'running'
|
39
37
|
chart_div(doc, 'pace', "Pace (#{select_unit('min/km')})")
|
40
38
|
else
|
@@ -274,9 +272,9 @@ EOT
|
|
274
272
|
# Don't plot frame for graph without data.
|
275
273
|
return if @empty_charts[field]
|
276
274
|
|
277
|
-
|
275
|
+
ViewFrame.new(title) {
|
278
276
|
doc.div({ 'id' => "#{field}_chart", 'class' => 'chart-placeholder'})
|
279
|
-
}
|
277
|
+
}.to_html(doc)
|
280
278
|
end
|
281
279
|
|
282
280
|
def hover_function(chart_id, y_label, y_unit)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = DeviceList.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2015 by Chris Schlaeger <cs@taskjuggler.org>
|
7
7
|
#
|
8
8
|
# This program is free software; you can redistribute it and/or modify
|
9
9
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -12,22 +12,18 @@
|
|
12
12
|
|
13
13
|
require 'fit4ruby'
|
14
14
|
|
15
|
-
require 'postrunner/
|
15
|
+
require 'postrunner/ViewFrame'
|
16
16
|
|
17
17
|
module PostRunner
|
18
18
|
|
19
19
|
class DeviceList
|
20
20
|
|
21
|
-
include ViewWidgets
|
22
|
-
|
23
21
|
def initialize(fit_activity)
|
24
22
|
@fit_activity = fit_activity
|
25
23
|
end
|
26
24
|
|
27
25
|
def to_html(doc)
|
28
|
-
|
29
|
-
devices.each { |d| d.to_html(doc) }
|
30
|
-
}
|
26
|
+
ViewFrame.new('Devices', 600, devices).to_html(doc)
|
31
27
|
end
|
32
28
|
|
33
29
|
def to_s
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = FlexiTable.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2015 by Chris Schlaeger <cs@taskjuggler.org>
|
7
7
|
#
|
8
8
|
# This program is free software; you can redistribute it and/or modify
|
9
9
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -252,6 +252,9 @@ module PostRunner
|
|
252
252
|
def to_html(doc)
|
253
253
|
index_table
|
254
254
|
|
255
|
+
doc.unique(:flexitable_style) {
|
256
|
+
doc.head { doc.style(style) }
|
257
|
+
}
|
255
258
|
doc.table(@html_attrs) {
|
256
259
|
@head_rows.each { |r| r.to_html(doc) }
|
257
260
|
@body_rows.each { |r| r.to_html(doc) }
|
@@ -318,6 +321,30 @@ module PostRunner
|
|
318
321
|
s + "\n"
|
319
322
|
end
|
320
323
|
|
324
|
+
def style
|
325
|
+
<<EOT
|
326
|
+
.flexitable {
|
327
|
+
width: 100%;
|
328
|
+
border: 2px solid #545454;
|
329
|
+
border-collapse: collapse;
|
330
|
+
font-size:11pt;
|
331
|
+
}
|
332
|
+
.ft_head_row {
|
333
|
+
background-color: #DEDEDE
|
334
|
+
}
|
335
|
+
.ft_even_row {
|
336
|
+
background-color: #FCFCFC
|
337
|
+
}
|
338
|
+
.ft_odd_row {
|
339
|
+
background-color: #F1F1F1
|
340
|
+
}
|
341
|
+
.ft_cell {
|
342
|
+
border: 1px solid #CCCCCC;
|
343
|
+
padding: 1px 3px;
|
344
|
+
}
|
345
|
+
EOT
|
346
|
+
end
|
347
|
+
|
321
348
|
end
|
322
349
|
|
323
350
|
end
|
@@ -19,18 +19,79 @@ module PostRunner
|
|
19
19
|
class HTMLBuilder
|
20
20
|
|
21
21
|
# Create a new HTMLBuilder object.
|
22
|
-
def initialize
|
22
|
+
def initialize(title)
|
23
23
|
# This is the Nokogiri Document that will store all the data.
|
24
24
|
@doc = Nokogiri::HTML::Document.new
|
25
25
|
# We only need to keep a stack of the currently edited nodes so we know
|
26
26
|
# where we are in the node tree.
|
27
27
|
@node_stack = []
|
28
|
+
@tags = []
|
29
|
+
|
30
|
+
@html = create_node('html') {
|
31
|
+
@head = create_node('head') {
|
32
|
+
create_node('meta', { 'http-equiv' => 'Content-Type',
|
33
|
+
'content' => 'text/html; charset=utf-8' })
|
34
|
+
create_node('title', title)
|
35
|
+
}
|
36
|
+
@body = create_node('body')
|
37
|
+
}
|
38
|
+
@node_stack << @html
|
39
|
+
@node_stack << @body
|
40
|
+
end
|
41
|
+
|
42
|
+
# Append nodes provided in block to head section of HTML document.
|
43
|
+
def head
|
44
|
+
@node_stack.push(@head)
|
45
|
+
yield if block_given?
|
46
|
+
unless @node_stack.pop == @head
|
47
|
+
raise ArgumentError, "node_stack corrupted in head"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Append nodes provided in block to body section of HTML document.
|
52
|
+
def body(*args)
|
53
|
+
@node_stack.push(@body)
|
54
|
+
args.each do |arg|
|
55
|
+
if arg.is_a?(Hash)
|
56
|
+
arg.each { |k, v| @body[k] = v }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
yield if block_given?
|
60
|
+
unless @node_stack.pop == @body
|
61
|
+
raise ArgumentError, "node_stack corrupted in body"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Only execute the passed block if the provided tag has not been added
|
66
|
+
# yet.
|
67
|
+
def unique(tag)
|
68
|
+
unless @tags.include?(tag)
|
69
|
+
@tags << tag
|
70
|
+
yield if block_given?
|
71
|
+
end
|
28
72
|
end
|
29
73
|
|
30
74
|
# Any call to an undefined method will create a HTML node of the same
|
31
75
|
# name.
|
32
|
-
def method_missing(method_name, *args)
|
33
|
-
|
76
|
+
def method_missing(method_name, *args, &block)
|
77
|
+
create_node(method_name.to_s, *args, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Only needed to comply with style guides. This all calls to unknown
|
81
|
+
# method will be handled properly. So, we always return true.
|
82
|
+
def respond_to?(method)
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
# Dump the HTML document as HTML formatted String.
|
87
|
+
def to_html
|
88
|
+
@doc.to_html
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def create_node(name, *args)
|
94
|
+
node = Nokogiri::XML::Node.new(name, @doc)
|
34
95
|
if (parent = @node_stack.last)
|
35
96
|
parent.add_child(node)
|
36
97
|
else
|
@@ -52,19 +113,6 @@ module PostRunner
|
|
52
113
|
@node_stack.pop
|
53
114
|
end
|
54
115
|
|
55
|
-
# Only needed to comply with style guides. This all calls to unknown
|
56
|
-
# method will be handled properly. So, we always return true.
|
57
|
-
def respond_to?(method)
|
58
|
-
true
|
59
|
-
end
|
60
|
-
|
61
|
-
# Dump the HTML document as HTML formatted String.
|
62
|
-
def to_html
|
63
|
-
@doc.to_html
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
116
|
def add_child(parent, node)
|
69
117
|
if parent
|
70
118
|
parent.add_child(node)
|
data/lib/postrunner/Main.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = Main.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2015 by Chris Schlaeger <cs@taskjuggler.org>
|
7
7
|
#
|
8
8
|
# This program is free software; you can redistribute it and/or modify
|
9
9
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -32,6 +32,8 @@ module PostRunner
|
|
32
32
|
def initialize(args)
|
33
33
|
@filter = nil
|
34
34
|
@name = nil
|
35
|
+
@attribute = nil
|
36
|
+
@value = nil
|
35
37
|
@activities = nil
|
36
38
|
@db_dir = File.join(ENV['HOME'], '.postrunner')
|
37
39
|
|
@@ -49,7 +51,7 @@ module PostRunner
|
|
49
51
|
|
50
52
|
opts.separator <<"EOT"
|
51
53
|
|
52
|
-
Copyright (c) 2014 by Chris Schlaeger
|
54
|
+
Copyright (c) 2014, 2015 by Chris Schlaeger
|
53
55
|
|
54
56
|
This program is free software; you can redistribute it and/or modify it under
|
55
57
|
the terms of version 2 of the GNU General Public License as published by the
|
@@ -133,9 +135,18 @@ list
|
|
133
135
|
records
|
134
136
|
List all personal records.
|
135
137
|
|
136
|
-
rename <ref>
|
137
|
-
|
138
|
-
the activity.
|
138
|
+
rename <new name> <ref>
|
139
|
+
For the specified activities replace current activity name with a
|
140
|
+
new name that describes the activity. By default the activity name
|
141
|
+
matches the FIT file name.
|
142
|
+
|
143
|
+
set <attribute> <value> <ref>
|
144
|
+
For the specified activies set the attribute to the given value. The
|
145
|
+
following attributes are supported:
|
146
|
+
|
147
|
+
name: The activity name (defaults to FIT file name)
|
148
|
+
type: The type of the activity
|
149
|
+
subtype: The subtype of the activity
|
139
150
|
|
140
151
|
show [ <ref> ]
|
141
152
|
Show the referenced FIT activity in a web browser. If no reference
|
@@ -144,9 +155,12 @@ show [ <ref> ]
|
|
144
155
|
summary <ref>
|
145
156
|
Display the summary information for the FIT file.
|
146
157
|
|
147
|
-
units
|
158
|
+
units <metric | statute>
|
148
159
|
Change the unit system.
|
149
160
|
|
161
|
+
htmldir <directory>
|
162
|
+
Change the output directory for the generated HTML files
|
163
|
+
|
150
164
|
|
151
165
|
<fit file> An absolute or relative name of a .FIT file.
|
152
166
|
|
@@ -154,26 +168,27 @@ units metric | statute
|
|
154
168
|
:1 is the newest imported activity
|
155
169
|
:-1 is the oldest imported activity
|
156
170
|
:1-2 refers to the first and second activity in the database
|
171
|
+
:1--1 refers to all activities
|
157
172
|
EOT
|
158
173
|
|
159
174
|
end
|
160
175
|
|
161
|
-
|
176
|
+
begin
|
177
|
+
parser.parse!(args)
|
178
|
+
rescue OptionParser::InvalidOption
|
179
|
+
Log.fatal "#{$!}"
|
180
|
+
end
|
162
181
|
end
|
163
182
|
|
164
183
|
def execute_command(args)
|
165
184
|
@activities = ActivitiesDB.new(@db_dir, @cfg)
|
166
|
-
|
167
|
-
Log.warn "Version upgrade detected. Database conversion started..."
|
168
|
-
@activities.generate_all_html_reports
|
169
|
-
@cfg.set_option(:version, VERSION)
|
170
|
-
Log.info "Version upgrade completed."
|
171
|
-
end
|
185
|
+
handle_version_update
|
172
186
|
|
173
187
|
case (cmd = args.shift)
|
174
188
|
when 'check'
|
175
189
|
if args.empty?
|
176
190
|
@activities.check
|
191
|
+
@activities.generate_all_html_reports
|
177
192
|
else
|
178
193
|
process_files_or_activities(args, :check)
|
179
194
|
end
|
@@ -201,9 +216,17 @@ EOT
|
|
201
216
|
@activities.show_records
|
202
217
|
when 'rename'
|
203
218
|
unless (@name = args.shift)
|
204
|
-
Log.fatal
|
219
|
+
Log.fatal 'You must provide a new name for the activity'
|
205
220
|
end
|
206
221
|
process_activities(args, :rename)
|
222
|
+
when 'set'
|
223
|
+
unless (@attribute = args.shift)
|
224
|
+
Log.fatal 'You must specify the attribute you want to change'
|
225
|
+
end
|
226
|
+
unless (@value = args.shift)
|
227
|
+
Log.fatal 'You must specify the new value for the attribute'
|
228
|
+
end
|
229
|
+
process_activities(args, :set)
|
207
230
|
when 'show'
|
208
231
|
if args.empty?
|
209
232
|
@activities.show_list_in_browser
|
@@ -214,6 +237,8 @@ EOT
|
|
214
237
|
process_activities(args, :summary)
|
215
238
|
when 'units'
|
216
239
|
change_unit_system(args)
|
240
|
+
when 'htmldir'
|
241
|
+
change_html_dir(args)
|
217
242
|
when nil
|
218
243
|
Log.fatal("No command provided. " +
|
219
244
|
"See 'postrunner -h' for more information.")
|
@@ -289,6 +314,8 @@ EOT
|
|
289
314
|
activity.dump(@filter)
|
290
315
|
when :rename
|
291
316
|
@activities.rename(activity, @name)
|
317
|
+
when :set
|
318
|
+
@activities.set(activity, @attribute, @value)
|
292
319
|
when :show
|
293
320
|
activity.show
|
294
321
|
when :summary
|
@@ -299,11 +326,7 @@ EOT
|
|
299
326
|
end
|
300
327
|
|
301
328
|
def read_fit_file(fit_file)
|
302
|
-
|
303
|
-
return Fit4Ruby::read(fit_file, @filter)
|
304
|
-
rescue StandardError
|
305
|
-
Log.error("Cannot read FIT file '#{fit_file}': #{$!}")
|
306
|
-
end
|
329
|
+
return Fit4Ruby::read(fit_file, @filter)
|
307
330
|
end
|
308
331
|
|
309
332
|
def change_unit_system(args)
|
@@ -317,6 +340,27 @@ EOT
|
|
317
340
|
end
|
318
341
|
end
|
319
342
|
|
343
|
+
def change_html_dir(args)
|
344
|
+
if args.length != 1
|
345
|
+
Log.fatal('You must specify a directory')
|
346
|
+
end
|
347
|
+
|
348
|
+
if @cfg[:html_dir] != args[0]
|
349
|
+
@cfg.set_option(:html_dir, args[0])
|
350
|
+
@activities.create_directories
|
351
|
+
@activities.generate_all_html_reports
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def handle_version_update
|
356
|
+
if @cfg.get_option(:version) != VERSION
|
357
|
+
Log.warn "PostRunner version upgrade detected."
|
358
|
+
@activities.handle_version_update
|
359
|
+
@cfg.set_option(:version, VERSION)
|
360
|
+
Log.info "Version upgrade completed."
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
320
364
|
end
|
321
365
|
|
322
366
|
end
|