postrunner 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 99ae2bd8b3b82d191caf48131b2859c4f4e29720
4
- data.tar.gz: ca9b0777ec1fe947bd2aeefa969b7c6e45d52d19
3
+ metadata.gz: bd33c5715662822de4739db4f57eace5c10e08fa
4
+ data.tar.gz: 6d64e7cd63503bf1a480c20ff2f3c1b2763e643b
5
5
  SHA512:
6
- metadata.gz: 89b8afc287b91f5f787e00da5b13642f2e2b86fd73296bd7d70580069698ce36c794773d16377d029eedb92f16293722c70605e79ab100c67442b51e4b7efd70
7
- data.tar.gz: 2089af8448f6151f7d3dfd1a1e9ccfeaa4849fa59e1f9b3b30ea8df798ac8b673e639beb494d07aef40c234abe3999a39764523defacf3524e61ee0263cfc2d8
6
+ metadata.gz: 1ce585498cb68bc2f2d623fd22058eb12187f247d17c39ed8e23f8f995ad44e0178e75a319a46d0de9f899de6f56b93d849ea669350b00e97faa638da19f7491
7
+ data.tar.gz: 3104d9e1f00f97629d85841a37838320037d95db794d77656cfc4d503c01ccec989e6f3ec13bd842fafb41587371fa8d23a333b1d7ee3832d242c896f96286d5
data/README.md CHANGED
@@ -32,12 +32,13 @@ full path to the directory that contains your FIT files. You can then
32
32
  import all files on the device.
33
33
 
34
34
  ```
35
- $ postrunner import /var/run/media/user/GARMIN/GARMIN/ACTIVITY/
35
+ $ postrunner import /run/media/$USER/GARMIN/GARMIN/ACTIVITY/
36
36
  ```
37
37
 
38
38
  The above command assumes that your device is mounted as
39
- /var/run/media/user. Please replace this with the path to your device.
40
- Files that have been imported previously will not be imported again.
39
+ /run/media/$USER. Please replace $USER with your login name and the
40
+ path with the path to your device. Files that have been imported
41
+ previously will not be imported again.
41
42
 
42
43
  ### Viewing FIT file data on the console
43
44
 
@@ -280,14 +280,16 @@ module PostRunner
280
280
  last_distance = nil
281
281
 
282
282
  @fit_activity.records.each do |record|
283
+ if record.timestamp.nil?
284
+ # All records must have a valid timestamp
285
+ Log.warn "Found a record without a valid timestamp"
286
+ return
287
+ end
283
288
  if record.distance.nil?
284
289
  # All records must have a valid distance mark or the activity does
285
290
  # not qualify for a personal record.
286
- Log.warn "Found a record without a valid distance"
287
- return
288
- end
289
- if record.timestamp.nil?
290
- Log.warn "Found a record without a valid timestamp"
291
+ Log.warn "Found a record at #{record.timestamp} without a " +
292
+ "valid distance"
291
293
  return
292
294
  end
293
295
 
@@ -67,7 +67,8 @@ module PostRunner
67
67
  def body(doc)
68
68
  @view.body {
69
69
  doc.div({ :class => 'main' }) {
70
- ViewFrame.new('Activities', 900, generate_table).to_html(doc)
70
+ ViewFrame.new('activities', 'Activities', 900,
71
+ generate_table).to_html(doc)
71
72
  }
72
73
  }
73
74
  end
@@ -33,17 +33,30 @@ module PostRunner
33
33
  end
34
34
 
35
35
  def to_s
36
- summary.to_s + "\n" + laps.to_s
36
+ summary.to_s + "\n" +
37
+ (@activity.note ? note.to_s + "\n" : '') +
38
+ laps.to_s
37
39
  end
38
40
 
39
41
  def to_html(doc)
40
42
  width = 600
41
- ViewFrame.new("Activity: #{@name}", width, summary).to_html(doc)
42
- ViewFrame.new('Laps', width, laps).to_html(doc)
43
+ ViewFrame.new('activity', "Activity: #{@name}",
44
+ width, summary).to_html(doc)
45
+ ViewFrame.new('note', 'Note', width, note,
46
+ true).to_html(doc) if @activity.note
47
+ ViewFrame.new('laps', 'Laps', width, laps, true).to_html(doc)
43
48
  end
44
49
 
45
50
  private
46
51
 
52
+ def note
53
+ t = FlexiTable.new
54
+ t.enable_frame(false)
55
+ t.body
56
+ t.row([ @activity.note ])
57
+ t
58
+ end
59
+
47
60
  def summary
48
61
  session = @fit_activity.sessions[0]
49
62
 
@@ -51,14 +51,23 @@ module PostRunner
51
51
  private
52
52
 
53
53
  def generate_html(doc)
54
- doc.head { doc.style(style) }
54
+ doc.unique(:activityview_style) {
55
+ doc.head {
56
+ [ 'jquery/jquery-2.1.1.min.js', 'flot/jquery.flot.js',
57
+ 'flot/jquery.flot.time.js' ].each do |js|
58
+ doc.script({ 'language' => 'javascript',
59
+ 'type' => 'text/javascript', 'src' => js })
60
+ end
61
+ doc.style(style)
62
+ }
63
+ }
55
64
  #doc.meta({ 'name' => 'viewport',
56
65
  # 'content' => 'width=device-width, ' +
57
66
  # 'initial-scale=1.0, maximum-scale=1.0, ' +
58
67
  # 'user-scalable=0' })
59
68
 
60
69
  body {
61
- doc.body({ :onload => 'init()' }) {
70
+ doc.body {
62
71
  # The main area with the 2 column layout.
63
72
  doc.div({ :class => 'main' }) {
64
73
  doc.div({ :class => 'left_col' }) {
@@ -157,15 +157,9 @@ module PostRunner
157
157
  def to_html(doc)
158
158
  doc.unique(:chartview_style) {
159
159
  doc.head {
160
- [ 'jquery/jquery-2.1.1.min.js', 'flot/jquery.flot.js',
161
- 'flot/jquery.flot.time.js' ].each do |js|
162
- doc.script({ 'language' => 'javascript',
163
- 'type' => 'text/javascript', 'src' => js })
164
- end
165
160
  doc.style(style)
166
161
  }
167
162
  }
168
-
169
163
  doc.script(java_script)
170
164
  @charts.each do |chart|
171
165
  label = chart[:label] + (chart[:unit] ? " (#{chart[:unit]})" : '')
@@ -333,7 +327,7 @@ EOT
333
327
  " inverseTransform: function (v) { return -v; } }"
334
328
  else
335
329
  # Set the minimum slightly below the lowest found value.
336
- s << ", yaxis: { min: #{0.9 * min_value} }"
330
+ s << ", yaxis: { min: #{0.9 * min_value} }" if min_value > 0.0
337
331
  end
338
332
  s << "});\n"
339
333
  s << lap_mark_labels(chart_id, start_time)
@@ -411,7 +405,7 @@ EOT
411
405
  # Don't plot frame for graph without data.
412
406
  return if @empty_charts[field]
413
407
 
414
- ViewFrame.new(title) {
408
+ ViewFrame.new("#{field}_chart", title, 600, nil, true) {
415
409
  doc.div({ 'id' => "#{field}_chart", 'class' => 'chart-placeholder'})
416
410
  }.to_html(doc)
417
411
  end
@@ -39,7 +39,8 @@ module PostRunner
39
39
  end
40
40
 
41
41
  def to_html(doc)
42
- ViewFrame.new("Data Sources", 1210, data_sources).to_html(doc)
42
+ ViewFrame.new('data_sources', "Data Sources", 1210, data_sources,
43
+ true).to_html(doc)
43
44
  end
44
45
 
45
46
  private
@@ -44,7 +44,7 @@ module PostRunner
44
44
  end
45
45
 
46
46
  def to_html(doc)
47
- ViewFrame.new('Devices', 600, devices).to_html(doc)
47
+ ViewFrame.new('devices', 'Devices', 600, devices, true).to_html(doc)
48
48
  end
49
49
 
50
50
  def to_s
@@ -42,7 +42,7 @@ module PostRunner
42
42
  # Add the list as HTML table to the specified doc.
43
43
  # @param doc [HTMLBuilder] HTML document
44
44
  def to_html(doc)
45
- ViewFrame.new("Events", 600, list).to_html(doc)
45
+ ViewFrame.new('events', 'Events', 600, list, true).to_html(doc)
46
46
  end
47
47
 
48
48
  private
@@ -96,8 +96,9 @@ module PostRunner
96
96
  'all' => 'All'
97
97
  }
98
98
 
99
- po_attr :device, :fit_file_name, :norecord, :name, :sport, :sub_sport,
100
- :timestamp, :total_distance, :total_timer_time, :avg_speed
99
+ po_attr :device, :fit_file_name, :norecord, :name, :note, :sport,
100
+ :sub_sport, :timestamp, :total_distance, :total_timer_time,
101
+ :avg_speed
101
102
  attr_reader :fit_activity
102
103
 
103
104
  # Create a new FFS_Activity object.
@@ -209,6 +210,8 @@ module PostRunner
209
210
  case attribute
210
211
  when 'name'
211
212
  self.name = value
213
+ when 'note'
214
+ self.note = value
212
215
  when 'type'
213
216
  load_fit_file
214
217
  unless ActivityTypes.values.include?(value)
@@ -150,6 +150,7 @@ module PostRunner
150
150
  def rename_activity(activity, name)
151
151
  activity.set('name', name)
152
152
  generate_html_index_pages
153
+ @store['records'].generate_html_reports if activity.has_records?
153
154
  end
154
155
 
155
156
  # Set the specified attribute of the given activity to a new value.
@@ -32,8 +32,14 @@ module PostRunner
32
32
  create_node('meta', { 'http-equiv' => 'Content-Type',
33
33
  'content' => 'text/html; charset=utf-8' })
34
34
  create_node('title', title)
35
+ @init_script = create_node('script', <<EOT
36
+ function postrunner_init() {
37
+ };
38
+ EOT
39
+ )
35
40
  }
36
41
  @body = create_node('body')
42
+ @body['onload'] = 'postrunner_init()'
37
43
  }
38
44
  @node_stack << @html
39
45
  @node_stack << @body
@@ -62,6 +68,12 @@ module PostRunner
62
68
  end
63
69
  end
64
70
 
71
+ def body_init_script(script)
72
+ # We have to insert the new script snippet after the last snippet and
73
+ # before the tailing "};\n".
74
+ @init_script.content = @init_script.text[0..-4] + script + "\n};\n"
75
+ end
76
+
65
77
  # Only execute the passed block if the provided tag has not been added
66
78
  # yet.
67
79
  def unique(tag)
@@ -40,28 +40,27 @@ module PostRunner
40
40
 
41
41
  return if (args = parse_options(args)).nil?
42
42
 
43
- create_directory(@db_dir, 'PostRunner data')
44
- @db = PEROBS::Store.new(File.join(@db_dir, 'database'))
45
- # Create a hash to store configuration data in the store unless it
46
- # exists already.
47
- cfg = (@db['config'] ||= @db.new(PEROBS::Hash))
48
- cfg['unit_system'] ||= :metric
49
- cfg['version'] ||= VERSION
50
- # We always override the data_dir as the user might have moved the data
51
- # directory. The only reason we store it in the DB is to have it
52
- # available throught the application.
53
- cfg['data_dir'] = @db_dir
54
- # Always update html_dir setting so that the DB directory can be moved
55
- # around by the user.
56
- cfg['html_dir'] = File.join(@db_dir, 'html')
57
-
58
- setup_directories
59
- if (errors = @db.check) != 0
60
- Log.fatal "Postrunner database is corrupted: #{errors} errors found"
43
+ unless $DEBUG
44
+ Kernel.trap('INT') do
45
+ begin
46
+ Log.fatal('Aborting on user request!')
47
+ rescue RuntimeError
48
+ exit 1
49
+ end
50
+ end
61
51
  end
62
- execute_command(args)
63
52
 
64
- @db.sync
53
+ begin
54
+ main(args)
55
+ rescue Exception => e
56
+ if e.is_a?(SystemExit) || e.is_a?(Interrupt)
57
+ $stderr.puts e.backtrace.join("\n") if $DEBUG
58
+ else
59
+ Log.fatal("#{e}\n#{e.backtrace.join("\n")}\n\n" +
60
+ "#{'*' * 79}\nYou have triggered a bug in PostRunner " +
61
+ "#{VERSION}!")
62
+ end
63
+ end
65
64
  end
66
65
 
67
66
  private
@@ -123,6 +122,9 @@ EOT
123
122
  'Directory for the activity database and related files') do |d|
124
123
  @db_dir = d
125
124
  end
125
+ opts.on('--debug', 'Enable debug mode') do
126
+ $DEBUG = true
127
+ end
126
128
  opts.on('-v', '--verbose',
127
129
  'Show internal messages helpful for debugging problems') do
128
130
  Log.level = Logger::DEBUG
@@ -176,6 +178,7 @@ set <attribute> <value> <ref>
176
178
  name: The activity name (defaults to FIT file name)
177
179
  norecord: Ignore all records from this activity (value must true
178
180
  or false)
181
+ note: A comment or summary of the activity
179
182
  type: The type of the activity
180
183
  subtype: The subtype of the activity
181
184
 
@@ -218,6 +221,31 @@ EOT
218
221
  end
219
222
  end
220
223
 
224
+ def main(args)
225
+ create_directory(@db_dir, 'PostRunner data')
226
+ @db = PEROBS::Store.new(File.join(@db_dir, 'database'))
227
+ # Create a hash to store configuration data in the store unless it
228
+ # exists already.
229
+ cfg = (@db['config'] ||= @db.new(PEROBS::Hash))
230
+ cfg['unit_system'] ||= :metric
231
+ cfg['version'] ||= VERSION
232
+ # We always override the data_dir as the user might have moved the data
233
+ # directory. The only reason we store it in the DB is to have it
234
+ # available throught the application.
235
+ cfg['data_dir'] = @db_dir
236
+ # Always update html_dir setting so that the DB directory can be moved
237
+ # around by the user.
238
+ cfg['html_dir'] = File.join(@db_dir, 'html')
239
+
240
+ setup_directories
241
+ if $DEBUG && (errors = @db.check) != 0
242
+ Log.fatal "Postrunner database is corrupted: #{errors} errors found"
243
+ end
244
+ execute_command(args)
245
+
246
+ @db.sync
247
+ end
248
+
221
249
  def setup_directories
222
250
  create_directory(@db['config']['html_dir'], 'HTML output')
223
251
 
@@ -425,7 +453,13 @@ EOT
425
453
  when :rename
426
454
  @ffs.rename_activity(activity, @name)
427
455
  when :set
428
- @ffs.set_activity_attribute(activity, @attribute, @value)
456
+ if @attribute == 'name'
457
+ # We have to handle the 'name' attribute as special case as we have
458
+ # to update some HTML reports as well.
459
+ @ffs.rename_activity(activity, @value)
460
+ else
461
+ @ffs.set_activity_attribute(activity, @attribute, @value)
462
+ end
429
463
  when :show
430
464
  activity.show
431
465
  when :sources
@@ -52,13 +52,14 @@ module PostRunner
52
52
  frame_width = 800
53
53
 
54
54
  @doc.div({ :class => 'main' }) {
55
- ViewFrame.new("All-time #{@sport_name} Records",
55
+ ViewFrame.new('all_time_records', "All-time #{@sport_name} Records",
56
56
  frame_width, @records.all_time).to_html(@doc)
57
57
 
58
58
  @records.yearly.sort{ |y1, y2| y2[0].to_i <=> y1[0].to_i }.
59
59
  each do |year, record|
60
60
  next if record.empty?
61
- ViewFrame.new("#{year} #{@sport_name} Records",
61
+ ViewFrame.new("year_#{year}_records",
62
+ "#{year} #{@sport_name} Records",
62
63
  frame_width, record).to_html(@doc)
63
64
  end
64
65
  }
@@ -43,9 +43,10 @@ module PostRunner
43
43
  doc.script({ 'src' => 'postrunner/trackview.js' })
44
44
  }
45
45
  doc.script(java_script)
46
+ doc.body_init_script('pr_trackview_init_xy()')
46
47
  }
47
48
 
48
- ViewFrame.new('Map', 600) {
49
+ ViewFrame.new('map', 'Map', 600, nil, true) {
49
50
  doc.div({ 'id' => 'map', 'class' => 'trackmap' })
50
51
  }.to_html(doc)
51
52
  end
@@ -75,9 +76,9 @@ EOT
75
76
  <<"EOT"
76
77
  #{track_points}
77
78
  #{lap_markers}
78
- function init() {
79
+ function pr_trackview_init_xy() {
79
80
  pr_trackview_init(#{center_long}, #{center_lat});
80
- }
81
+ };
81
82
  EOT
82
83
  end
83
84
 
@@ -26,7 +26,8 @@ module PostRunner
26
26
  def to_html(doc)
27
27
  return nil if @fit_activity.user_profiles.empty?
28
28
 
29
- ViewFrame.new('User Profile', 600, profile).to_html(doc)
29
+ ViewFrame.new('user_profile', 'User Profile', 600, profile,
30
+ true).to_html(doc)
30
31
  end
31
32
 
32
33
  def to_s
@@ -16,16 +16,21 @@ module PostRunner
16
16
  class ViewFrame
17
17
 
18
18
  # Create a ViewFrame object.
19
+ # @param id [String] ID that is unique with regards to the rendered HTML
20
+ # page.
19
21
  # @param title [String] Title/heading of the framed box
20
22
  # @param width [Fixnum or nil] Width of the frame. Use nil to set no
21
23
  # width.
22
24
  # @param content [Any object that respons to to_html] Object to frame
23
25
  # @param &block [HTMLBuilder actions]
24
- def initialize(title, width = 600, content = nil, &block)
26
+ def initialize(id, title, width = 600, content = nil, hide_button = false,
27
+ &block)
28
+ @id = id
25
29
  @title = title
26
30
  @content = content
27
31
  @block = block
28
32
  @width = width
33
+ @hide_button = hide_button
29
34
  end
30
35
 
31
36
  # Generate the HTML code for the frame and the enclosing content.
@@ -33,16 +38,41 @@ module PostRunner
33
38
  def to_html(doc)
34
39
  doc.unique(:viewframe_style) {
35
40
  # Add the necessary style sheet snippets to the document head.
36
- doc.head { doc.style(style) }
41
+ doc.head {
42
+ doc.style(style)
43
+ doc.script({ 'src' => 'postrunner/postrunner.js' })
44
+ }
45
+ }
46
+ doc.head {
47
+ doc.script(<<"EOT"
48
+ function init_viewframe_#{@id}() {
49
+ if (readCookie('postrunner_checkbox_#view_#{@id}') == 'false') {
50
+ $('#checkbox-#{@id}').attr('checked', false);
51
+ $('#view_#{@id}').hide();
52
+ } else {
53
+ $('#checkbox-#{@id}').attr('checked', true);
54
+ $('#view_#{@id}').show();
55
+ };
56
+ };
57
+ EOT
58
+ )
59
+ doc.body_init_script("init_viewframe_#{@id}();")
37
60
  }
38
61
 
39
62
  attr = { 'class' => 'widget_frame' }
40
63
  attr['style'] = "width: #{@width}px" if @width
41
64
  doc.div(attr) {
42
65
  doc.div({ 'class' => 'widget_frame_title' }) {
43
- doc.b(@title)
66
+ doc.div('style' => 'float:left') { doc.b(@title) }
67
+ if @hide_button
68
+ doc.div('style' => 'float:right') {
69
+ doc.input('id' => "checkbox-#{@id}", 'type' => "checkbox",
70
+ 'onclick' =>
71
+ "pr_view_frame_toggle(this, \"#view_#{@id}\");")
72
+ }
73
+ end
44
74
  }
45
- doc.div {
75
+ doc.div('class' => 'view_frame', 'id' => "view_#{@id}") {
46
76
  # The @content holds an object that must respond to to_html to
47
77
  # generate the HTML code.
48
78
  if @content
@@ -81,8 +111,10 @@ module PostRunner
81
111
  }
82
112
  .widget_frame_title {
83
113
  font-size:13pt;
84
- padding-bottom: 5px;
85
- text-align: left;
114
+ height: 23px;
115
+ }
116
+ .view_frame {
117
+ padding-top: 5px;
86
118
  }
87
119
  EOT
88
120
  end
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = version.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, 2016 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
@@ -11,5 +11,5 @@
11
11
  #
12
12
 
13
13
  module PostRunner
14
- VERSION = "0.1.0"
14
+ VERSION = "0.2.0"
15
15
  end
@@ -1,7 +1,7 @@
1
1
  /*
2
- * TrackView.rb -- PostRunner - Manage the data from your Garmin sport devices.
2
+ * TrackView.js -- PostRunner - Manage the data from your Garmin sport devices.
3
3
  *
4
- * Copyright (c) 2014 by Chris Schlaeger <cs@taskjuggler.org>
4
+ * Copyright (c) 2014, 2015, 2016 by Chris Schlaeger <cs@taskjuggler.org>
5
5
  *
6
6
  * This program is free software; you can redistribute it and/or modify
7
7
  * it under the terms of version 2 of the GNU General Public License as
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
  spec.required_ruby_version = '>=2.0'
21
21
 
22
- spec.add_dependency 'fit4ruby', '~> 0.0.9'
22
+ spec.add_dependency 'fit4ruby', '~> 0.0.11'
23
23
  spec.add_dependency 'perobs', '~> 2.2'
24
24
  spec.add_dependency 'nokogiri', '~> 1.6'
25
25
 
@@ -0,0 +1,171 @@
1
+ require 'time'
2
+
3
+ desc 'Generate the CHANGELOG file'
4
+ task :changelog do
5
+
6
+ class Entry
7
+
8
+ attr_reader :type
9
+
10
+ def initialize(ref, author, time, message)
11
+ @ref = ref
12
+ @author = author
13
+ @time = time
14
+ @message = message
15
+ if (m = /New: (.*)/i.match(@message))
16
+ @type = :feature
17
+ @message = m[1]
18
+ elsif (m = /Fix: (.*)/i.match(@message))
19
+ @type = :bugfix
20
+ @message = m[1]
21
+ else
22
+ @type = :other
23
+ end
24
+ end
25
+
26
+ def to_s
27
+ " * #{@message}\n"
28
+ end
29
+
30
+ end
31
+
32
+ class Release
33
+
34
+ attr_reader :date, :version, :tag
35
+
36
+ def initialize(tag, predecessor)
37
+ @tag = tag
38
+ # We only support release tags in the form X.X.X
39
+ @version = /\d+\.\d+\.\d+/.match(tag)
40
+
41
+ # Construct a Git range.
42
+ interval = predecessor ? "#{predecessor.tag}..#{@tag}" : @tag
43
+
44
+ # Get the date of the release
45
+ git_show_tag = `git show #{tag}`.encode('UTF-8', invalid: :replace,
46
+ undef: :replace)
47
+ date = Time.parse(/Date: (.*)/.match(git_show_tag)[1]).utc
48
+ @date = date.strftime("%Y-%m-%d")
49
+
50
+ @entries = []
51
+ # Use -z option for git-log to get 0 bytes as separators.
52
+ `git log -z #{interval}`.split("\0").each do |commit|
53
+ # We ignore merges.
54
+ next if commit =~ /^Merge: \d*/
55
+
56
+ ref, author, time, _, message = commit.split("\n", 5)
57
+ ref = ref[/commit ([0-9a-f]+)/, 1]
58
+ author = author[/Author: (.*)/, 1].strip
59
+ time = Time.parse(time[/Date: (.*)/, 1]).utc
60
+ # Eleminate git-svn-id: lines
61
+ message.gsub!(/git-svn-id: .*\n/, '')
62
+ # Eliminate Signed-off-by: lines
63
+ message.gsub!(/Signed-off-by: .*\n/, '')
64
+ message.strip!
65
+ @entries << Entry.new(ref, author, time, message)
66
+ end
67
+ end
68
+
69
+ def empty?
70
+ @entries.empty?
71
+ end
72
+
73
+ def to_s
74
+ s = ''
75
+ if hasFeatures? || hasFixes?
76
+ if hasFeatures?
77
+ s << "== New Features\n\n"
78
+ @entries.each do |entry|
79
+ s << entry.to_s if entry.type == :feature
80
+ end
81
+ s << "\n"
82
+ end
83
+ if hasFixes?
84
+ s << "== Bug Fixes\n\n"
85
+ @entries.each do |entry|
86
+ s << entry.to_s if entry.type == :bugfix
87
+ end
88
+ s << "\n"
89
+ end
90
+ else
91
+ @entries.each do |entry|
92
+ s << entry.to_s
93
+ end
94
+ end
95
+ s
96
+ end
97
+
98
+ private
99
+
100
+ def hasFeatures?
101
+ @entries.each do |entry|
102
+ return true if entry.type == :feature
103
+ end
104
+ false
105
+ end
106
+
107
+ def hasFixes?
108
+ @entries.each do |entry|
109
+ return true if entry.type == :bugfix
110
+ end
111
+ false
112
+ end
113
+
114
+ end
115
+
116
+ class ChangeLog
117
+
118
+ def initialize
119
+ @releases = []
120
+ predecessor = nil
121
+ getReleaseVersions.each do |version|
122
+ @releases << (predecessor = Release.new(version, predecessor))
123
+ end
124
+ end
125
+
126
+ def to_s
127
+ s = ''
128
+ @releases.reverse.each do |release|
129
+ next if release.empty?
130
+
131
+ # We use RDOC markup syntax to generate a title
132
+ if release.version
133
+ s << "= Release #{release.version} (#{release.date})\n\n"
134
+ else
135
+ s << "= Next Release (Some Day)\n\n"
136
+ end
137
+ s << release.to_s + "\n"
138
+ end
139
+ s
140
+ end
141
+
142
+ private
143
+
144
+ # 'git tag' is not sorted numerically. This function implements a
145
+ # numerical comparison for tag versions of the format 'release-X.X.X'. X
146
+ # can be a multi-digit number.
147
+ def compareTags(a, b)
148
+
149
+ def versionToComparable(v)
150
+ /\d+\.\d+\.\d+/.match(v)[0].split('.').map{ |l| sprintf("%03d", l.to_i)}.
151
+ join('.')
152
+ end
153
+
154
+ versionToComparable(a) <=> versionToComparable(b)
155
+ end
156
+
157
+ def getReleaseVersions
158
+ # Get list of release tags from Git repository
159
+ releaseVersions = `git tag`.split("\n").map { |r| r.chomp }.
160
+ delete_if { |r| ! (/v\d+\.\d+\.\d+/ =~ r) }.
161
+ sort{ |a, b| compareTags(a, b) }
162
+ releaseVersions << 'HEAD'
163
+ end
164
+
165
+ end
166
+
167
+ File.open('CHANGELOG', 'w+') do |changelog|
168
+ changelog.puts ChangeLog.new.to_s
169
+ end
170
+
171
+ end
@@ -0,0 +1,7 @@
1
+ require 'rake'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Run all RSpec tests in the spec directory'
5
+ RSpec::Core::RakeTask.new(:test) do |t|
6
+ t.pattern = 'spec/*_spec.rb'
7
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postrunner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Schlaeger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-29 00:00:00.000000000 Z
11
+ date: 2016-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fit4ruby
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.0.9
19
+ version: 0.0.11
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.0.9
26
+ version: 0.0.11
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: perobs
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -304,6 +304,8 @@ files:
304
304
  - spec/PostRunner_spec.rb
305
305
  - spec/View_spec.rb
306
306
  - spec/spec_helper.rb
307
+ - tasks/changelog.rake
308
+ - tasks/test.rake
307
309
  homepage: https://github.com/scrapper/postrunner
308
310
  licenses:
309
311
  - GNU GPL version 2