postrunner 0.10.1 → 0.11.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: 800922f3ea3d5ff42ba5e706be3e2bff5dbed0ad
4
- data.tar.gz: 20e85ae14a306f2c662defabaa3c18b19d6bcec6
3
+ metadata.gz: d8de6ac38768c84e90eb47ddb49429d7a74160b1
4
+ data.tar.gz: 7816eba71ed5b6f90eef7d7ae15080c19d548624
5
5
  SHA512:
6
- metadata.gz: 3e4983e623b4c575937d1b9d419b7f874532affe4ebd8e1e849f58027fa59459365b38804396c794f88649949cc78ed43fa0019ef164c4f8a0f8ef6c3409e19d
7
- data.tar.gz: 3b35e7874e9418bc1d3cd39b1d8574ad169f69ca17b5375dee6423287697b475955126d29503e72242e0a04cfc893daf2195f45649e6c82594acf40548b06551
6
+ metadata.gz: 3302269a229d363cfc75bbea304879f06c37e8c2e6c1706ebece3ce6b662b02a026315df59e7494de230c5c3cab3fb81b925bd0ef89f4b81b1280805b422b138
7
+ data.tar.gz: e8179a0afb54a171ba200480cf498523688acffa9f3568b8a2ae1e272a5bdc4c863449c6c43b201dafe6223afeb64f64ca3520799e93317f4aec8114cdcb64f2
@@ -95,6 +95,7 @@ module PostRunner
95
95
  'challenge' => 'Challenge',
96
96
  'indoor_skiing' => 'Indoor Skiing',
97
97
  'cardio_training' => 'Cardio Training',
98
+ 'virtual_activity' => 'Virtual Activity',
98
99
  'all' => 'All'
99
100
  }
100
101
 
@@ -379,7 +380,7 @@ module PostRunner
379
380
  end
380
381
 
381
382
  def activity_sub_type
382
- ActivitySubTypes[@sub_sport] || 'Undefined'
383
+ ActivitySubTypes[@sub_sport] || 'Undefined "#{@sub_sport}"'
383
384
  end
384
385
 
385
386
  def distance(timestamp, unit_system)
@@ -87,6 +87,7 @@ module PostRunner
87
87
  { :metric => 'km', :statute => 'mi'}) ])
88
88
  end
89
89
  t.row([ 'Time:', secsToHMS(session.total_timer_time) ])
90
+ t.row([ 'Elapsed Time:', secsToHMS(session.total_elapsed_time) ])
90
91
  t.row([ 'Avg. Speed:',
91
92
  local_value(session, 'avg_speed', '%.1f %s',
92
93
  { :metric => 'km/h', :statute => 'mph' }) ])
@@ -145,6 +146,14 @@ module PostRunner
145
146
  t.row([ 'Aerobic Training Effect:', session.total_training_effect ])
146
147
  end
147
148
 
149
+ if (p_epoc = peak_epoc) > 0.0
150
+ t.row([ 'Peak EPOC:', "%.0f ml/kg" % p_epoc ])
151
+ end
152
+
153
+ if (trimp = trimp_exp) > 0.0
154
+ t.row([ 'TRIMP:', trimp.round ])
155
+ end
156
+
148
157
  rec_info = @fit_activity.recovery_info
149
158
  t.row([ 'Ignored Recovery Time:',
150
159
  rec_info ? secsToDHMS(rec_info * 60) : '-' ])
@@ -361,6 +370,89 @@ module PostRunner
361
370
  end
362
371
  end
363
372
 
373
+ def trimp_exp
374
+ # According to Bannister/Morton
375
+ # TRIMPexp = sum(D x HRr x 0.64e^y)
376
+ # Where
377
+ #
378
+ # D is the duration in minutes at a particular Heart Rate
379
+ # HRr is the Heart Rate as a fraction of Heart Rate Reserve
380
+ # y is the HRr multiplied by 1.92 for men and 1.67 for women.
381
+ return 0.0 unless (user_data = @fit_activity.user_data.first)
382
+
383
+ user_profile = @fit_activity.user_profiles.first
384
+ hr_zones = @fit_activity.heart_rate_zones.first
385
+ session = @fit_activity.sessions[0]
386
+
387
+ unless (user_profile && (rest_hr = user_profile.resting_heart_rate)) ||
388
+ (hr_zones && (rest_hr = hr_zones.resting_heart_rate))
389
+ # We must have a valid resting heart rate to compute TRIMP.
390
+ return 0.0
391
+ end
392
+ unless (user_data && (max_hr = user_data.max_hr)) ||
393
+ (hr_zones && (max_hr = hr_zones.max_heart_rate))
394
+ # We must have a valid maximum heart rate to compute TRIMP.
395
+ return 0.0
396
+ end
397
+ unless (session && session.avg_heart_rate &&
398
+ avg_hr = session.avg_heart_rate)
399
+ return 0.0
400
+ end
401
+
402
+ sex_factor = user_data.gender == 'male' ? 1.92 : 1.67
403
+
404
+ # Instead of using the average heart rate for the whole activity we
405
+ # apply the equation for each heart rate sample and accumulate them.
406
+ sum = 0.0
407
+ prev_timestamp = nil
408
+ @activity.fit_activity.records.each do |r|
409
+ # We need a valid timestmap and a valid previous timestamp. If they
410
+ # are more than 10 seconds appart we discard the values as there was
411
+ # likely a pause in the activity.
412
+ if prev_timestamp && r.timestamp && r.heart_rate &&
413
+ r.timestamp - prev_timestamp <= 10
414
+ # Compute the heart rate as fraction of the heart rate reserve
415
+ hr_r = (r.heart_rate - rest_hr).to_f / (max_hr - rest_hr)
416
+
417
+ duration_min = (r.timestamp - prev_timestamp) / 60.0
418
+ #sum += duration_min * hr_r * 0.64 * Math.exp(sex_factor * hr_r)
419
+ sum += duration_min * hr_r * 0.64 * Math.exp(sex_factor * hr_r)
420
+ end
421
+
422
+ prev_timestamp = r.timestamp
423
+ end
424
+
425
+ sum
426
+
427
+ # Alternatively here is an avarage HR based implementation
428
+ # hr_r = (session.avg_heart_rate - rest_hr).to_f / (max_hr - rest_hr)
429
+ # duration_min = session.total_elapsed_time / 60.0
430
+ # duration_min * hr_r * 0.64 * Math.exp(sex_factor * hr_r)
431
+ end
432
+
433
+ def peak_epoc
434
+ # Peak EPOC value according to figure 2 in the following white paper by
435
+ # FristBeat:
436
+ # https://www.firstbeat.com/wp-content/uploads/2015/10/white_paper_training_effect.pdf
437
+ unless @fit_activity.physiological_metrics &&
438
+ (pm = @fit_activity.physiological_metrics.last) &&
439
+ (te = pm.aerobic_training_effect)
440
+ return 0.0
441
+ end
442
+ unless (user_data = @fit_activity.user_data.first) &&
443
+ (ac = user_data.activity_class)
444
+ return 0.0
445
+ end
446
+
447
+ # The following formula was taken from
448
+ # http://www.movescount.com/apps/app10020404-EPOC_from_TE
449
+ # It apparently approximates the graph in figure 2 in the FirstBeat
450
+ # paper.
451
+ epoc = -11.0 + te * (20.0 + te * (-47.0/4.0 + te * (3.0 - te / 4.0)))
452
+ (-102.0 + te * (759.0 / 4.0 + te * (-2867.0 / 24.0 +
453
+ te * (139.0 / 4.0 - 73.0 / 24.0 * te))) - epoc) / 10.0 * ac + epoc
454
+ end
455
+
364
456
  end
365
457
 
366
458
  end
@@ -290,10 +290,10 @@ EOT
290
290
  last_value = nil
291
291
  last_timestamp = nil
292
292
  @activity.fit_activity.records.each do |r|
293
- if last_timestamp && (r.timestamp - last_timestamp) > 5.0
293
+ if last_timestamp && (r.timestamp - last_timestamp) > 10.0
294
294
  # We have a gap in the values that is longer than 5 seconds. We'll
295
295
  # finish the line and start a new one later.
296
- data_set << [ (r.timestamp - start_time + 1).to_i * 1000, nil ]
296
+ data_set << [ (last_timestamp - start_time + 1).to_i * 1000, nil ]
297
297
  end
298
298
  if (value = r.get_as(chart[:id], chart[:unit] || ''))
299
299
  if chart[:id] == 'pace'
@@ -310,7 +310,7 @@ EOT
310
310
  min_value = value if (min_value.nil? || min_value > value)
311
311
  end
312
312
  end
313
- unless last_value.nil? && value.nil?
313
+ if value
314
314
  data_set << [ (r.timestamp - start_time).to_i * 1000, value ]
315
315
  end
316
316
  last_value = value
@@ -337,7 +337,7 @@ EOT
337
337
  #{chart[:colors] ? "color: \"#{chart[:colors]}\"," : ''}
338
338
  lines: { show: true#{chart[:id] == 'pace' ? '' :
339
339
  ', fill: true'} } } ],
340
- { xaxis: { mode: "time" },
340
+ { xaxis: { mode: "time", min: 0.0 },
341
341
  grid: { markings: lap_marks, hoverable: true }
342
342
  EOT
343
343
  if chart[:id] == 'pace'
@@ -417,7 +417,7 @@ EOT
417
417
  " fillColor: \"#{chart[:colors][index][0]}\", " +
418
418
  " fill: true, radius: 2 } }"
419
419
  end.join(', ')
420
- s << "], { xaxis: { mode: \"time\" }, " +
420
+ s << "], { xaxis: { mode: \"time\", min: 0.0 }, " +
421
421
  (chart[:id] == 'gct_balance' ? gct_balance_yaxis(data_sets) : '') +
422
422
  " grid: { markings: lap_marks, hoverable: true } });\n"
423
423
  s << lap_mark_labels(chart_id, start_time)
@@ -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, 2015 by Chris Schlaeger <cs@taskjuggler.org>
6
+ # Copyright (c) 2014, 2015, 2018 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,6 +12,7 @@
12
12
 
13
13
  require 'uri'
14
14
  require 'net/http'
15
+ require 'net/https'
15
16
 
16
17
  module PostRunner
17
18
 
@@ -20,7 +21,7 @@ module PostRunner
20
21
  # devices pick up this file under GARMIN/GARMIN/REMOTESW/EPO.BIN.
21
22
  class EPO_Downloader
22
23
 
23
- @@URI = URI('http://omt.garmin.com/Rce/ProtobufApi/EphemerisService/GetEphemerisData')
24
+ @@URI = URI('https://omt.garmin.com/Rce/ProtobufApi/EphemerisService/GetEphemerisData')
24
25
  # This is the payload of the POST request. It was taken from
25
26
  # http://www.kluenter.de/garmin-ephemeris-files-and-linux/. It may contain
26
27
  # a product ID or serial number.
@@ -33,7 +34,8 @@ module PostRunner
33
34
 
34
35
  # Create an EPO_Downloader object.
35
36
  def initialize
36
- @http = Net::HTTP.new(@@URI.host, @@URI.port)
37
+ @https = Net::HTTP.new(@@URI.host, @@URI.port)
38
+ @https.use_ssl = true
37
39
  @request = Net::HTTP::Post.new(@@URI.path, initheader = @@HEADER)
38
40
  @request.body = @@POST_DATA
39
41
  end
@@ -57,7 +59,7 @@ module PostRunner
57
59
 
58
60
  def get_epo_from_server
59
61
  begin
60
- res = @http.request(@request)
62
+ res = @https.request(@request)
61
63
  rescue => e
62
64
  Log.error "Extended Prediction Orbit (EPO) data download error: " +
63
65
  e.message
@@ -93,6 +93,7 @@ module PostRunner
93
93
  'challenge' => 'Challenge',
94
94
  'indoor_skiing' => 'Indoor Skiing',
95
95
  'cardio_training' => 'Cardio Training',
96
+ 'virtual_activity' => 'Virtual Activity',
96
97
  'all' => 'All'
97
98
  }
98
99
 
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = FFS_Device.rb -- PostRunner - Manage the data from your Garmin sport devices.
5
5
  #
6
- # Copyright (c) 2015, 2016 by Chris Schlaeger <cs@taskjuggler.org>
6
+ # Copyright (c) 2015, 2016, 2018 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
@@ -23,7 +23,7 @@ module PostRunner
23
23
  # dashes. All objects are transparently stored in the PEROBS::Store.
24
24
  class FFS_Device < PEROBS::Object
25
25
 
26
- attr_persist :activities, :monitorings, :short_uid, :long_uid
26
+ attr_persist :activities, :monitorings, :metrics, :short_uid, :long_uid
27
27
 
28
28
  # Create a new FFS_Device object.
29
29
  # @param p [PEROBS::Handle] p
@@ -41,6 +41,7 @@ module PostRunner
41
41
  def restore
42
42
  attr_init(:activities) { @store.new(PEROBS::Array) }
43
43
  attr_init(:monitorings) { @store.new(PEROBS::Array) }
44
+ attr_init(:metrics) { @store.new(PEROBS::Array) }
44
45
  end
45
46
 
46
47
  # Add a new FIT file for this device.
@@ -61,6 +62,11 @@ module PostRunner
61
62
  entities = @monitorings
62
63
  type = 'monitoring'
63
64
  new_entity_class = FFS_Monitoring
65
+ elsif fit_entity.is_a?(Fit4Ruby::Metrics)
66
+ entity = metrics_by_file_name(File.basename(fit_file_name))
67
+ entities = @metrics
68
+ type = 'metrics'
69
+ new_entity_class = FFS_Metrics
64
70
  else
65
71
  Log.fatal "Unsupported FIT entity #{fit_entity.class}"
66
72
  end
@@ -130,6 +136,13 @@ module PostRunner
130
136
  @monitorings.find { |a| a.fit_file_name == file_name }
131
137
  end
132
138
 
139
+ # Return the metrics with the given file name.
140
+ # @param file_name [String] Base name of the fit file.
141
+ # @return [FFS_Activity] Corresponding FFS_Metrics or nil.
142
+ def metrics_by_file_name(file_name)
143
+ @metrics.find { |a| a.fit_file_name == file_name }
144
+ end
145
+
133
146
  # Return all monitorings that overlap with the time interval given by
134
147
  # from_time and to_time.
135
148
  # @param from_time [Time] start time of the interval
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = FitFileStore.rb -- PostRunner - Manage the data from your Garmin sport devices.
5
5
  #
6
- # Copyright (c) 2014, 2015, 2016 by Chris Schlaeger <cs@taskjuggler.org>
6
+ # Copyright (c) 2014, 2015, 2016, 2018 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
@@ -80,20 +80,15 @@ module PostRunner
80
80
  # @return [FFS_Activity or FFS_Monitoring] Corresponding entry in the
81
81
  # FitFileStore or nil if file could not be added.
82
82
  def add_fit_file(fit_file_name, fit_entity = nil, overwrite = false)
83
- md5sum = FitFileStore.calc_md5_sum(fit_file_name)
84
- if @store['fit_file_md5sums'].include?(md5sum)
85
- # The FIT file is already stored in the DB.
86
- return nil unless overwrite
87
- end
88
-
89
- # If we the file hasn't been read yet, read it in as a
83
+ # If the file hasn't been read yet, read it in as a
90
84
  # Fit4Ruby::Activity or Fit4Ruby::Monitoring entity.
91
85
  unless fit_entity
92
86
  return nil unless (fit_entity = read_fit_file(fit_file_name))
93
87
  end
94
88
 
95
89
  unless [ Fit4Ruby::Activity,
96
- Fit4Ruby::Monitoring_B ].include?(fit_entity.class)
90
+ Fit4Ruby::Monitoring_B,
91
+ Fit4Ruby::Metrics ].include?(fit_entity.class)
97
92
  Log.fatal "Unsupported FIT file type #{fit_entity.class}"
98
93
  end
99
94
 
@@ -451,7 +446,8 @@ module PostRunner
451
446
 
452
447
  def extract_fit_file_id(fit_entity)
453
448
  unless (fid = fit_entity.file_id)
454
- Log.fatal 'FIT file has no file_id section'
449
+ Log.error 'FIT file has no file_id section'
450
+ return nil
455
451
  end
456
452
 
457
453
  if fid.manufacturer == 'garmin' &&
@@ -470,10 +466,20 @@ module PostRunner
470
466
  }
471
467
  end
472
468
  end
473
- Log.fatal "Fit entity has no device info for 0"
469
+ Log.error "Fit entity has no device info for 0"
470
+ return nil
474
471
  else
475
472
  # And for all properly developed devices we can just look at the
476
473
  # file_id section.
474
+ if fid.manufacturer.nil? ||
475
+ fid.manufacturer[0..'Undocumented value'.length - 1] ==
476
+ 'Undocumented value'
477
+ Log.error "Cannot store FIT files for unknown manufacturer " +
478
+ fid.manufacturer
479
+ return nil
480
+ end
481
+ fid.serial_number ||= 0
482
+
477
483
  return {
478
484
  :manufacturer => fid.manufacturer,
479
485
  :product => fid.garmin_product || fid.product,
@@ -57,7 +57,9 @@ module PostRunner
57
57
  create_directory(@db_dir, 'PostRunner data')
58
58
  ensure_flat_file_db
59
59
  @db = PEROBS::Store.new(File.join(@db_dir, 'database'),
60
- { :engine => PEROBS::FlatFileDB })
60
+ { :engine => PEROBS::FlatFileDB,
61
+ :progressmeter =>
62
+ PEROBS::ConsoleProgressMeter.new })
61
63
  # Create a hash to store configuration data in the store unless it
62
64
  # exists already.
63
65
  cfg = (@db['config'] ||= @db.new(PEROBS::Hash))
@@ -252,7 +254,7 @@ weekly [ <YYYY-MM-DD> ]
252
254
  week. If no date is given, yesterday's week will be used.
253
255
 
254
256
 
255
- <fit file> An absolute or relative name of a .FIT file.
257
+ <fit file> An absolute or relative name of a .FIT or .fit file.
256
258
 
257
259
  <ref> The index or a range of indexes to activities in the database.
258
260
  :1 is the newest imported activity
@@ -437,12 +439,12 @@ EOT
437
439
 
438
440
  def process_files(files_or_dirs, command)
439
441
  if files_or_dirs.empty?
440
- Log.abort("You must provide at least one .FIT file name.")
442
+ Log.abort("You must provide at least one .FIT or .fit file name.")
441
443
  end
442
444
 
443
445
  files_or_dirs.each do |fod|
444
446
  if File.directory?(fod)
445
- Dir.glob(File.join(fod, '*.FIT')).each do |file|
447
+ Dir.glob(File.join(fod, '*.FIT'), File::FNM_CASEFOLD).each do |file|
446
448
  process_file(file, command)
447
449
  end
448
450
  else
@@ -472,6 +474,12 @@ EOT
472
474
  # @return [TrueClass, FalseClass] true if file was successfully imported,
473
475
  # false otherwise
474
476
  def import_fit_file(fit_file_name)
477
+ md5sum = FitFileStore.calc_md5_sum(fit_file_name)
478
+ if @ffs.store['fit_file_md5sums'].include?(md5sum)
479
+ # The FIT file is already stored in the DB.
480
+ return nil unless @force
481
+ end
482
+
475
483
  begin
476
484
  fit_entity = Fit4Ruby.read(fit_file_name)
477
485
  rescue Fit4Ruby::Error
@@ -24,14 +24,14 @@ module PostRunner
24
24
  end
25
25
 
26
26
  def to_html(doc)
27
- return nil if @fit_activity.user_profiles.empty?
27
+ return nil if @fit_activity.user_data.empty?
28
28
 
29
29
  ViewFrame.new('user_profile', 'User Profile', 600, profile,
30
30
  true).to_html(doc)
31
31
  end
32
32
 
33
33
  def to_s
34
- return '' if @fit_activity.user_profiles.empty?
34
+ return '' if @fit_activity.user_data.empty?
35
35
  profile.to_s
36
36
  end
37
37
 
@@ -39,31 +39,44 @@ module PostRunner
39
39
 
40
40
  def profile
41
41
  t = FlexiTable.new
42
- profile = @fit_activity.user_profiles.first
43
- if profile.height
42
+
43
+ user_data = @fit_activity.user_data.first
44
+ user_profile = @fit_activity.user_profiles.first
45
+ hr_zones = @fit_activity.heart_rate_zones.first
46
+
47
+ if user_data.height
44
48
  unit = { :metric => 'm', :statute => 'ft' }[@unit_system]
45
- height = profile.get_as('height', unit)
49
+ height = user_data.get_as('height', unit)
46
50
  t.cell('Height:', { :width => '40%' })
47
51
  t.cell("#{'%.2f' % height} #{unit}", { :width => '60%' })
48
52
  t.new_row
49
53
  end
50
- if profile.weight
54
+ if user_data.weight
51
55
  unit = { :metric => 'kg', :statute => 'lbs' }[@unit_system]
52
- weight = profile.get_as('weight', unit)
56
+ weight = user_data.get_as('weight', unit)
53
57
  t.row([ 'Weight:', "#{'%.1f' % weight} #{unit}" ])
54
58
  end
55
- t.row([ 'Gender:', profile.gender ]) if profile.gender
56
- t.row([ 'Age:', "#{profile.age} years" ]) if profile.age
57
- t.row([ 'Max. Heart Rate:', "#{profile.max_hr} bpm" ]) if profile.max_hr
58
- if (lthr = profile.running_lactate_threshold_heart_rate)
59
+ t.row([ 'Gender:', user_data.gender ]) if user_data.gender
60
+ t.row([ 'Age:', "#{user_data.age} years" ]) if user_data.age
61
+ if (user_profile && (rest_hr = user_profile.resting_heart_rate)) ||
62
+ (hr_zones && (rest_hr = hr_zones.resting_heart_rate))
63
+ t.row([ 'Resting Heart Rate:', "#{rest_hr} bpm" ])
64
+ end
65
+ if (max_hr = user_data.max_hr) ||
66
+ (max_hr = hr_zones.max_heart_rate)
67
+ t.row([ 'Max. Heart Rate:', "#{max_hr} bpm" ])
68
+ end
69
+ if (lthr = user_data.running_lactate_threshold_heart_rate)
59
70
  t.row([ 'Running LTHR:', "#{lthr} bpm" ])
60
71
  end
61
- if profile.activity_class
62
- t.row([ 'Activity Class:', profile.activity_class ])
72
+ if (activity_class = user_data.activity_class)
73
+ t.row([ 'Activity Class:', activity_class ])
74
+ end
75
+ if (metmax = user_data.metmax)
76
+ t.row([ 'METmax:', "#{metmax} MET" ])
63
77
  end
64
- if profile.metmax
65
- t.row([ 'METmax:', "#{profile.metmax} MET" ])
66
- t.row([ 'VO2max:', "#{'%.1f' % (profile.metmax * 3.5)} ml/kg/min" ])
78
+ if (vo2max = @fit_activity.vo2max)
79
+ t.row([ 'VO2max:', "#{'%.1f' % vo2max} ml/kg/min" ])
67
80
  end
68
81
  t
69
82
  end
@@ -11,5 +11,5 @@
11
11
  #
12
12
 
13
13
  module PostRunner
14
- VERSION = '0.10.1'
14
+ VERSION = '0.11.0'
15
15
  end
@@ -28,8 +28,8 @@ operating systems as well.}
28
28
  spec.require_paths = ["lib"]
29
29
  spec.required_ruby_version = '>=2.0'
30
30
 
31
- spec.add_dependency 'fit4ruby', '~> 2.0.0'
32
- spec.add_dependency 'perobs', '~> 4.0.0'
31
+ spec.add_dependency 'fit4ruby', '~> 3.2.0'
32
+ spec.add_dependency 'perobs', '~> 4.1.0'
33
33
  spec.add_dependency 'nokogiri', '~> 1.6'
34
34
 
35
35
  spec.add_development_dependency 'bundler', '~> 1.6'
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.10.1
4
+ version: 0.11.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: 2018-01-17 00:00:00.000000000 Z
11
+ date: 2019-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fit4ruby
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.0.0
19
+ version: 3.2.0
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: 2.0.0
26
+ version: 3.2.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: perobs
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 4.0.0
33
+ version: 4.1.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 4.0.0
40
+ version: 4.1.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: nokogiri
43
43
  requirement: !ruby/object:Gem::Requirement