postrunner 0.0.6 → 0.0.7
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 +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
|