postrunner 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -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 head(doc)
28
- [ 'jquery/jquery-2.1.1.min.js', 'flot/jquery.flot.js',
29
- 'flot/jquery.flot.time.js' ].each do |js|
30
- doc.script({ 'language' => 'javascript', 'type' => 'text/javascript',
31
- 'src' => js })
32
- end
33
- doc.style(style)
34
- doc.script(java_script)
35
- end
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
- def div(doc)
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
- frame(doc, title) {
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/ViewWidgets'
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
- frame(doc, 'Devices') {
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
- node = Nokogiri::XML::Node.new(method_name.to_s, @doc)
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)
@@ -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
- Replace the FIT file name with a more meaningful name that describes
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 metric | statute
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
- parser.parse!(args)
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
- if @cfg.get_option(:version) != VERSION
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 "You must provide a new name for the activity"
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
- begin
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