postrunner 0.1.0 → 0.2.0
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/README.md +4 -3
- data/lib/postrunner/Activity.rb +7 -5
- data/lib/postrunner/ActivityListView.rb +2 -1
- data/lib/postrunner/ActivitySummary.rb +16 -3
- data/lib/postrunner/ActivityView.rb +11 -2
- data/lib/postrunner/ChartView.rb +2 -8
- data/lib/postrunner/DataSources.rb +2 -1
- data/lib/postrunner/DeviceList.rb +1 -1
- data/lib/postrunner/EventList.rb +1 -1
- data/lib/postrunner/FFS_Activity.rb +5 -2
- data/lib/postrunner/FitFileStore.rb +1 -0
- data/lib/postrunner/HTMLBuilder.rb +12 -0
- data/lib/postrunner/Main.rb +55 -21
- data/lib/postrunner/RecordListPageView.rb +3 -2
- data/lib/postrunner/TrackView.rb +4 -3
- data/lib/postrunner/UserProfileView.rb +2 -1
- data/lib/postrunner/ViewFrame.rb +38 -6
- data/lib/postrunner/version.rb +2 -2
- data/misc/postrunner/trackview.js +2 -2
- data/postrunner.gemspec +1 -1
- data/tasks/changelog.rake +171 -0
- data/tasks/test.rake +7 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd33c5715662822de4739db4f57eace5c10e08fa
|
4
|
+
data.tar.gz: 6d64e7cd63503bf1a480c20ff2f3c1b2763e643b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 /
|
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
|
-
/
|
40
|
-
|
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
|
|
data/lib/postrunner/Activity.rb
CHANGED
@@ -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
|
287
|
-
|
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
|
|
@@ -33,17 +33,30 @@ module PostRunner
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def to_s
|
36
|
-
summary.to_s + "\n" +
|
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}",
|
42
|
-
|
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.
|
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
|
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' }) {
|
data/lib/postrunner/ChartView.rb
CHANGED
@@ -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
|
data/lib/postrunner/EventList.rb
CHANGED
@@ -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(
|
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, :
|
100
|
-
:timestamp, :total_distance, :total_timer_time,
|
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)
|
data/lib/postrunner/Main.rb
CHANGED
@@ -40,28 +40,27 @@ module PostRunner
|
|
40
40
|
|
41
41
|
return if (args = parse_options(args)).nil?
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
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
|
-
|
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}
|
61
|
+
ViewFrame.new("year_#{year}_records",
|
62
|
+
"#{year} #{@sport_name} Records",
|
62
63
|
frame_width, record).to_html(@doc)
|
63
64
|
end
|
64
65
|
}
|
data/lib/postrunner/TrackView.rb
CHANGED
@@ -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
|
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
|
29
|
+
ViewFrame.new('user_profile', 'User Profile', 600, profile,
|
30
|
+
true).to_html(doc)
|
30
31
|
end
|
31
32
|
|
32
33
|
def to_s
|
data/lib/postrunner/ViewFrame.rb
CHANGED
@@ -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,
|
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 {
|
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
|
-
|
85
|
-
|
114
|
+
height: 23px;
|
115
|
+
}
|
116
|
+
.view_frame {
|
117
|
+
padding-top: 5px;
|
86
118
|
}
|
87
119
|
EOT
|
88
120
|
end
|
data/lib/postrunner/version.rb
CHANGED
@@ -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.
|
14
|
+
VERSION = "0.2.0"
|
15
15
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
/*
|
2
|
-
* TrackView.
|
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
|
data/postrunner.gemspec
CHANGED
@@ -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.
|
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
|
data/tasks/test.rake
ADDED
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.
|
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
|
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.
|
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.
|
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
|