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.
@@ -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